In my blog's model, I have a boolean flag to determine if a post is "featured" or not. Featured posts will be displayed in a different way.
To retrieve the posts I have defined two model managers as below:
class PublishedManager(models.Manager):
def get_queryset(self):
return super(PublishedManager, self).get_queryset().filter(status='published')
class FeaturedManager(models.Manager):
def get_queryset(self):
return super(FeaturedManager, self).get_queryset().filter(feature=True).order_by('-publish')[:3]
and in the views.py this is how I pass them to the template:
class PostListView(ListView):
queryset = Post.published.all()
context_object_name = 'posts'
paginate_by = 10
def get_context_data(self, *args, **kwargs):
context = super(PostListView, self).get_context_data(*args, **kwargs)
context['featured'] = Post.featured.all()
return context
Now in my template I have a section for featured posts and another section for normal posts. Normal posts are easy, but I want to display the first featured post — which is going to be the most recent one — in a separate container, and the last two in another one. There always will be the last 3 featured posts displayed.
Here's the code for template to display the first featured post:
<div class="jumbotron p-4 p-md-5 text-white rounded bg-dark">
<div class="col-md-10 px-0">
<h1 class="display-4 font-italic">The Title of Newest Featured Post</h1>
<p class="lead my-3">The Body of the Newest Featured Post</p>
</div>
</div>
My question is, how to access the first, second and third posts in featured object?
You can use the following logic
<p class="lead my-3">{{ featured | first }}</p>
OR
<p class="lead my-3">{{ featured[0] }}</p>
...
<p class="lead my-3">{{ featured[1] }}</p>
etc.
Of course you can create your own templatetags and use them whenever you need them.
Create a new folder under your project root folder with the name templatetags
Add an empty __init__.py file
Create a new file with the name second.py
Add the following code
import os
from django import template
register = template.Library()
#register.filter
def second(list):
return list[1]
On the top of your template html file where you want to use it add the following line {% load second %}
Use the following code {{ featured | second:ADD_THE_LIST }}
In case that you want to read more about the templatetags https://docs.djangoproject.com/en/3.2/howto/custom-template-tags/
Apparently the answer is to use queryset.index in the template:
featured.0 for the first, featured.1 for second and so on.
Related
I'm new to Django, and I'm working on my first real (i.e., non-tutorial) project. I have class-based ListViews on four models, and the lists can be filtered in various ways. The user can click on anything in a filtered list to get a DetailView of the item. This is all straightforward and works fine.
I would like to have Previous and Next buttons on the detail pages that allow the user to step through the current filtered set in the default order (which is not date or id). I've found various bits and pieces on StackOverflow and elsewhere that look like parts of a solution, but I haven't been able to figure out how to make them all work together in my views.
Here is slightly simplified code for one of my tables. "Works" are various items (plays, operas, ballets, etc.) that were performed in one of two theaters.
models.py
class Work(models.Model):
title = models.CharField(max_length=100)
sort_title = models.CharField(max_length=100)
genre = models.CharField(max_length=50)
class Meta:
ordering = ['sort_title']
The field sort_title strips off articles at the beginnings of titles (which are in German and French) and deals with accented characters and the like, so that the titles will sort correctly alphabetically. This is the order of the filtered sets, and I want to retain that order for the Previous and Next buttons.
views.py
class WorkList(ListView):
context_object_name = 'works'
model = Work
paginate_by = 50
template_name = 'works.html'
def get_queryset(self):
query = self.request.GET.get('q')
if query is not None:
return Work.objects.filter(Q(title__icontains=query))
else:
return Work.objects.all()
class WorkDetail(DetailView):
context_object_name = 'work'
model = Work
template_name = 'work.html'
At the moment, the user can only filter Works by title, but I may add the possibility of filtering by genre (hence the Q, which I'm already using for other views). I'm using Bootstrap 4, and I would use some version of the following for the buttons on the detail pages:
<ul class="pagination pt-3">
<li class="page-link">Previous</li>
<li class="page-link ml-auto">Next</li>
</ul>
But since I don't know how to make this work yet, I don't know what the URLs will be.
I've tried django-next-previous, which works well in the shell, but I can't figure out how to make it work in my views. Since I want to preserve the filtered queryset from the ListView and use it in the DetailView, I've also experimented with this approach to saving the queryset in the session: https://gist.github.com/bsnux/4672788. But I haven't been able to figure out how to use this to pass the queryset between the two views.
Any help would be welcome!
The queries
Previous page:
Work.objects.filter(sort_title__lt=self.object.sort_title).reverse().values('id')[:1]
Next page:
Work.objects.filter(sort_title__gt=self.object.sort_title).values('id')[:1]
Next page if sort_title is not unique - note the gte rather than gt:
Work.objects.filter(sort_title__gte=self.object.sort_title).exclude(id=self.object.id).values('id')[:1]
Explanation
filter(...): The Work object is ordered by the sort_title field by default. So by asking for a sort_title that is greater than the sort_title of the current object, so we will find the next Work object in the set.
self.object is how we access the current object from a DetailView.
values('id'): Only select the values we need to reverse() the URL to a different WorkDetail. I'm making a presumption that this is the id field, but if it's not, it can be substituted with another field.
[:1]: Basically just adds LIMIT 1 to the SQL query. We only need the next in the set.
This all keeps the queries lightweight.
Note that these queries only work (using __lt with reverse() for previous page and __gt for next page) because the default ordering of the Work model is by sort_title ascending.
Putting it together
Given that your ListView and DetailView will be sharing the same queryset logic, it might make sense to use a mixin, for example:
class WorkQueryMixin:
def get_queryset(self):
query = self.request.GET.get('q')
if query is not None:
return Work.objects.filter(Q(title__icontains=query))
else:
return Work.objects.all()
The query parameter could be returned in a different way (e.g. through the session data).
Getting it into context, for example for the next page:
class WorkDetail(WorkQueryMixin, DetailView):
context_object_name = 'work'
model = Work
template_name = 'work.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
next_id = (
self.get_queryset()
.filter(sort_title__gt=self.object.sort_title)
.values('id')[:1]
)
# There may be no next page
if next_id:
context['next_id'] = next_id[0]['id']
return context
Many thanks to elyas for the great detailed initial answer and for taking the time to coach me through the details of the implementation. I now have functional Previous and Next buttons that retain the current queryset. Here is the code I ended up with, including both Previous and Next buttons, with a genre filter added:
views.py
class WorkQueryMixin:
def get_queryset(self):
query = self.request.GET.get('q')
if query is not None:
return Work.objects.filter(Q(title__icontains=query) |
Q(genre__icontains=query))
else:
return Work.objects.all()
class WorkList(WorkQueryMixin, ListView):
context_object_name = 'works'
model = Work
paginate_by = 50
template_name = 'my_app/works.html'
class WorkDetail(WorkQueryMixin, DetailView):
context_object_name = 'work'
model = Work
template_name = 'my_app/work.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
prev_pk = (
self.get_queryset()
.filter(sort_title__lt=self.object.sort_title)
.reverse().values('pk')[:1]
)
# There may be no next page
if prev_pk:
context['prev_pk'] = prev_pk[0]['pk']
next_pk = (
self.get_queryset()
.filter(sort_title__gt=self.object.sort_title)
.values('pk').values('pk')[:1]
)
# There may be no next page
if next_pk:
context['next_pk'] = next_pk[0]['pk']
return context
In the template for the Works list view, using classes from Bootstrap 4:
<ul>
{% for work in works %}
<a href="{% url 'my_app:work' work.pk %}?{{
request.GET.urlencode }}" class="list-group-item list-group-
item-action">
{% if work.title == "No title" %}
{{work.title}}
{% else %}
<em>{{work.title}}</em>
{% endif %} ({{work.genre}})
</a>
{% empty %}
<p>No works match this search</p>
{% endfor %}
</ul>
And in the template for the Work detail view:
<ul class="pagination pt-3">
{% if prev_pk %}
<li class="page-link"><a href="{% url 'my_app:work' prev_pk %}?{{
request.GET.urlencode }}">Previous</a></li>
{% endif %}
{% if next_pk %}
<li class="page-link ml-auto"><a href="{% url 'my_app:work' next_pk
%}?{{ request.GET.urlencode }}">Next</a></li>
{% endif %}
</ul>
So I am trying to change my form's model Datefield output to the Datepicker similar to DatepickerWidget in CreateView
The forms are generated using a html template:
{% for field in form %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<span class="text-danger small">{{ field.error }}</span>
</div>
<label class="control-label col-sm-2">{{field.label_tag}}</label>
<div class="col-sm-10">{{field}}</div>
</div>
{% endfor %}
Here is the Views with what I tried:
class newenv_form(generic.CreateView):
model = Environment
fields =['name', 'description', 'creation_date', 'status','status_update_date']
template_name = 'catalogue/new_env.html'
#Does not work
def get_form(self, form):
form = super(newenv_form, self)
form.fields['creation_date','status_update_date'].widget = forms.DateInput(attrs={'class':'datepicker'})
return form
Here is what worked but it is a dropdown datepicker that is limited in choices
def get_form(self):
'''add date picker in forms'''
from django.forms.extras.widgets import SelectDateWidget
form = super(EnvironmentCreateView, self).get_form()
form.fields['creation_date'].widget = SelectDateWidget()
return form
Note that I remove form_class which was causing problems
UPDATE: On Django 3.1, you can find SelectDateWidget within django.forms.widgets
Try to change the following line in the method get_form:
form = super(newenv_form, self)
to:
form = super(newenv_form, self).get_form(form)
And please follow the conventions and use PascalCase for class names in python.
You could call this class EnvironmentCreateView. Further generic view classes could be called for example EnvironmentListView, EnvironmentDetailView, EnvironmentUpdateView, EnvironmentDeleteView.
Using the same pattern for all your model classes will produce comprehensible code.
EDIT (2017-10-24):
Regarding your comment here is a further explanation. Although it is hard to give a correct remote diagnosis, I'd suggest the following changes:
class EnvironmentCreateView(generic.CreateView):
# class attributes ...
def get_form(self, form_class=None):
form = super(EnvironmentCreateView, self).get_form(form_class)
# further code ...
The essential changes are in bold. The class name is changed to meet the conventions. Also the parameter form is changed to form_class to meet the convetions, too. I emphasise conventions in particular, because it makes the code very comprehensible to other people familiar with the framework.
The important change is that form_class has the initial value None.
That should solve the problem with the error.
In the body of the method you call the parent method with super and write after that your custom code.
Please check the documentation for generic.CreateView. It inherits, among others, from generic.FormMixin. That is the class with the method get_form.
I have a class called Features in my models.py. In my html, I am displaying a list on the right that excludes two of these Features, one is the active feature that has been selected, the other is the most recently added since they are the main content of my page. The remaining Features in the list are displayed by date and do show what I am expecting.
Now, I want to single out the first, second and third Features (title only) in THAT list so I can place them in their own separate divs - because each has unique css styling. There are probably numerous ways of doing this, but I can't seem to figure any of them out.
This is a link to my project to give a better idea of what I want (basically trying to get the content in those colored boxes on the right.)
I'm just learning Django (and Python really), so thanks for your patience and help!
HTML
{% for f in past_features %}
{% if f.title != selected_feature.title %}
{% if f.title != latest_feature.title %}
<h1>{{ f.title }}</h1>
{% endif %}
{% endif %}
{% endfor %}
VIEWS
def feature_detail(request, pk):
selected_feature = get_object_or_404(Feature, pk=pk)
latest_feature = Feature.objects.order_by('-id')[0]
past_features = Feature.objects.order_by('-pub_date')
test = Feature.objects.last()
context = {'selected_feature': selected_feature,
'latest_feature': latest_feature,
'past_features': past_features,
'test': test}
return render(request, 'gp/feature_detail.html', context)
MODELS
class Feature(models.Model):
title = models.CharField(db_index=True, max_length=100, default='')
content = models.TextField(default='')
pub_date = models.DateTimeField(db_index=True, default=datetime.now, blank=True)
def __str__(self):
return self.title
def __iter__(self):
return [
self.id,
self.title ]
You can either store the first three Features in separate variables in your context or add checks to your template loop like {% if forloop.first %} or {% if forloop.counter == 2 %}.
If all you want is to not have the
selected_feature
latest_feature
these two records out of the past_features queryset, then you can use exclude on the past_features query and pass the id's of the selected_features and latest_feature objects.
The views.py would look like:
def feature_detail(request, pk):
selected_feature = get_object_or_404(Feature, pk=pk)
latest_feature = Feature.objects.order_by('-id')[0]
# Collect all the id's present in the latest_feature
excluded_ids = [record.pk for record in latest_feature]
excluded_ids.append(selected_feature.pk)
#This would only return the objects excluding the id present in the list
past_features = Feature.objects.order_by('-pub_date').exclude(id__in=excluded_ids)
test = Feature.objects.last()
context = {'selected_feature': selected_feature,
'latest_feature': latest_feature,
'past_features': past_features,
'test': test}
return render(request, 'gp/feature_detail.html', context)
Django provides a rich ORM and well documented, go through the Queryset options for further information.
For access to a specific object in Django templates see following example:
For access to first object you can use {{ students.0 }}
For access to second object you can use {{ students.1 }}
For access to a specific field for example firstname in object 4 you can use {{ students.3.firstname }}
For access to image field in second object you can use {{ students.1.photo.url }}
For access to id in first object you can use {{ students.0.id }}
When a user registers for my app.I receive this error when he reaches the profile page.
The 'image' attribute has no file associated with it.
Exception Type: ValueError
Error during template rendering
In template C:\o\mysite\pet\templates\profile.html, error at line 6
1 <h4>My Profile</h4>
2
3 {% if person %}
4 <ul>
5 <li>Name: {{ person.name }}</li>
6 <br><img src="{{ person.image.url }}">
Traceback Switch back to interactive view
File "C:\o\mysite\pet\views.py" in Profile
71. return render(request,'profile.html',{'board':board ,'person':person})
I think this error happens because my template requires a image and seen he just registered he can't add a image unless he go to the edit page and adds a page then he can access the profile page.
My profile.html
<h4>My Profile</h4>
{% if person %}
<ul>
<li>Name: {{ person.name }}</li>
<br><img src="{{ person.image.url }}">
</ul>
{% endif %}
My Profile function at views.py
def Profile(request):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('world:LoginRequest'))
board = Board.objects.filter(user=request.user)
person = Person.objects.get(user=request.user)
return render(request,'profile.html',{'board':board ,'person':person})
I tried this solution by creating a 2 instance of Person object and separating them at my template with a if but it didn't succeed.
<h4>My Profile</h4>
{% if person %}
<ul>
<li>Name: {{ person.name }}</li>
</ul>
{% endif %}
{% if bob %}
<ul>
<br><img src="{{ bob.image.url }}">
</ul>
My solutions to the Profile function
def Profile(request):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('world:LoginRequest'))
board = Board.objects.filter(user=request.user)
person = Person.objects.get(user=request.user)
bob = Person.objects.get(user=request.user)
return render(request,'profile.html',{'board':board ,'person':person,'bob':bob})
I'm been reading the documentation for Built-in template tags and filters I think a solution here is to use ( and ) template tag but I can't seem to use it properly.
How can I configure this template to make picture an option. If their are no picture leave it but display the persons name.
Thank you for helping me
bob and person are the same object,
person = Person.objects.get(user=request.user)
bob = Person.objects.get(user=request.user)
So you can use just person for it.
In your template, check image exist or not first,
{% if person.image %}
<img src="{{ person.image.url }}">
{% endif %}
The better approach which would not violate DRY is to add a helper method to the model class like:
#property
def image_url(self):
if self.image and hasattr(self.image, 'url'):
return self.image.url
and use default_if_none template filter to provide default url:
<img src="{{ object.image_url|default_if_none:'#' }}" />
My dear friend, others solvings are good but not enough because If user hasn't profile picture you should show default image easily (not need migration). So you can follow below steps:
Add this method to your person model:
#property
def get_photo_url(self):
if self.photo and hasattr(self.photo, 'url'):
return self.photo.url
else:
return "/static/images/user.jpg"
You can use any path (/media, /static etc.) but don't forget putting default user photo as user.jpg to your path.
And change your code in template like below:
<img src="{{ profile.get_photo_url }}" class="img-responsive thumbnail " alt="img">
Not exactly what OP was looking for, but another possible solution would be to set a default value for ImageField:
class Profile(models.Model):
# rest of the fields here
image = models.ImageField(
upload_to='profile_pics/',
default='profile_pics/default.jpg')
you have two choices :
first one:
in the model field try to put a default image value , like this :
PRF_image = models.ImageField(upload_to='profile_img', blank=True, null=True , default='profile_img/925667.jpg')
the second one (recommended) :
add a custom method inside your class model like the following , to return PRF_image url if exist or return empty string if not :
PRF_image = models.ImageField(upload_to='profile_img', blank=True, null=True )
#property
def my_PRF_image(self):
if self.PRF_image :
return self.PRF_image.url
return ''
and inside your template you can use :
{{ your_object.my_PRF_image }}
i hope this helpful .
You can also use the Python 3 built-in function getattr to create your new property:
#property
def image_url(self):
"""
Return self.photo.url if self.photo is not None,
'url' exist and has a value, else, return None.
"""
if self.image:
return getattr(self.photo, 'url', None)
return None
and use this property in your template:
<img src="{{ my_obj.image_url|default_if_none:'#' }}" />
Many way to solve this issue
Try below code
models.py # under Person class
#property
def imageURL(self):
if self.image:
return self.image.url
else:
return 'images/placeholder.png'
html file
<img src="{% static person.imageURL %}" class="thumbnail" />
Maybe this helps but my database didn't save on of the pictures for the object displayed on the page.
As that object in models.py has blank=False and also I am looping through object, it constantly gave an error until I added a replacement picture in the admin for the database to render.
This error also arises when any one or more items doesn't have an image added and the rest items do. To fix this:
class Product(models.Model):
pname = models.CharField(max_length=30)
price = models.IntegerField()
img = models.ImageField(null = True,blank = True)
def __str__(self):
return self.pname
#property
def imageURL(self):
try:
url = self.img.url
except:
url=''
return url
I had a similar problem , but my issue was with the form in HTML template. if you don't set the enctype = "multipart/form-data" attribute then it does not upload the image hence the reason for the error
I am new to the Django web framework.
I have a template that displays the list of all objects. I have all the individual objects listed as a link (object title), clicking on which I want to redirect to another page that shows the object details for that particular object.
I am able to list the objects but not able to forward the object/object id to the next template to display the details.
views.py
def list(request):
listings = listing.objects.all()
return render_to_response('/../templates/listings.html',{'listings':listings})
def detail(request, id):
#listing = listing.objects.filter(owner__vinumber__exact=vinumber)
return render_to_response('/../templates/listing_detail.html')
and templates as:
list.html
{% for listing in object_list %}
<!--<li> {{ listing.title }} </li>-->
{{ listing.title}}<br>
{% endfor %}
detail.html
{{ id }}
The variables that you pass in the dictionary of render_to_response are the variables that end up in the template. So in detail, you need to add something like {'listing': MyModel.objects.get(id=vinumber)}, and then the template should say {{ listing.id }}. But hat'll crash if the ID doesn't exist, so it's better to use get_object_or_404.
Also, your template loops over object_list but the view passes in listings -- one of those must be different than what you said if it's currently working....
Also, you should be using the {% url %} tag and/or get_absolute_url on your models: rather than directly saying href="{{ listing.id }}", say something like href="{% url listing-details listing.id %}", where listing-details is the name of the view in urls.py. Better yet is to add a get_absolute_url function to your model with the permalink decorator; then you can just say href="{{ listing.get_absolute_url }}", which makes it easier to change your URL structure to look nicer or use some attribute other than the database id in it.
You should check the #permalink decorator. It enables you to give your models generated links based on your urls pattern and corresponding view_function.
For example:
# example model
class Example(models.Model):
name = models.CharField("Name", max_length=255, unique=True)
#more model fields here
#the permalink decorator with get_absolute_url function
#models.permalink
def get_absolute_url(self):
return ('example_view', (), {'example_name': self.name})
#example view
def example_view(request, name, template_name):
example = get_object_or_404(Example, name=name)
return render_to_response(template_name, locals(),
context_instance=RequestContext(request))
#example urls config
url(r'^(?P<name>[-\w]+)/$', 'example_view', {'template_name': 'example.html'}, 'example_view')
Now you can do in your templates something like this:
<a href={{ example.get_absolute_url }}>{{ example.name }}</a>
Hope this helps.
In your detail method, just pass the listing into your template like so:
def detail(request, id):
l = listing.objects.get(pk=id)
return render_to_response('/../templates/listing_detail.html', {'listing':l})