How to access a special field in a subquery? - python

I have an image field in Django, but I'm using an StdImage field:
class Photo(TimestampedModel):
image = StdImageField(upload_to='photos', variations={'thumbnail': (200, 200), 'large': (1024, 1024)})
Normally, in a template I can use the following to access a 'variation':
<img alt="" src="{{ object.myimage.thumbnail.url }}"/>
However, I am using a subquery to retrieve the latest photo for an article (there could be many photos, or none) and I have no other way than to retrieve the image as a single value (as I can't retrieve multiple values in a subquery).
newest_photo = Photo.objects.filter(industry__type=OuterRef('pk')).order_by('-date')
list = Article.objects.filter(published=True).annotate(image=Subquery(newest_photo.values('image')[:1]))
This all works well. Except for one thing. I am only able to retrieve the regular image, not the variation (which I would normally access by object.image.thumbnail.url). Any idea how to access this variation?

I believe you can avoid subqueries in this situation and keep it simple.
#view.py
newest_photo = Photo.objects.filter(industry__type=OuterRef('pk')).order_by('-date')[:1]
articles = Article.objects.filter(published=True)
article_list = []
for article in list:
article_list.append({
#here you add the article fields you want from the model
#or 'article':article.to_dict()
'img':newest_photo,
#just to be sure add the url here as well
'thumbnail_url':newest_photo.thumbnail.url
})
context = {'articles':article_list}
Now you can send it as context and access the url with article.img.thumbnail.url or article.thumbnail_url

Do you have a lot of Photo objects per Article? If no, the best way is to use prefetch_related to prefetch all Photos (order them using Prefetch object), and then get the first with simple index
list(article.photos.all())[0]
Note that you have to use list(), either way Django will decide to make another query, instead of using already prefetched objects. You can add a method to Article (latest_photo) to do this and call prefetch_related in view, then use the method in template.

Related

Permanently replace value in Django

After extensive googling, I still havent come up with an effecient way to solve this.
Im creating a website using Django. I have a db which contains time data, more specifically dates. The value for "the present" is set to 3000-01-01 (YYYY-MM-DD) as is common practice for time-querying.
What I want to do is display a string like "Now" or "Present" or any other value instead of the date 3000-01-01. Is there some sort of global override anywhere that I can use? Seems like a waste to hard-code it in every view/template.
Cheers!
Since this is rendering, I would advice against "subclassing" the DateField such that it renders 'now' instead of the original date(..) object: it will make calculations in the model layer more cumbersome.
Probably a good way to deal with this is implementing a template filter [Django-doc], for example we can construct a file:
# app/templatetags/nowdate.py
from django import template
register = template.Library()
PRESENT = date(3000, 1, 1)
#register.filter
def nowdate(value):
if value == PRESENT:
return 'present'
return value
The templatetags directory of the app application, needs to contain an __init__.py file as well (an empty file), and the app of course needs to be part of the INSTALLED_APPS in the settings.py.
Then we can use it in the template like:
<!-- app/templates/app/some_template.html -->
{{ some_model.some_date_field|nowdate }}
Here we thus fetch the some_date_field of the some_model variable (this attribute is thus a date(..) object), and we pass it through the nowdate filter we have constructed such that, if it is 3000-01-01, it is replaced by the 'present' string.
The advantage here is that if we later change our mind about what date the "present" is, we can easily change it in the template filter, furthermore we can easily extend it, for example by adding a 'past', 'future', etc.

How to add extra field data to a form field in Wtforms

I am trying to add extra data to a form field in wtforms.
I have to create a text field which has an associated unit with it (eg - meter/sec). How do I add the meter/sec string to the form field?
Is there any way to pass a dictionary or something to add data to the field that i can access in the template?
There is a not very well known parameter, description= to the field constructor. Though it purports to be for help text, the framework itself doesn't care what you put in there (and indeed doesn't use it anywhere at all, other than passing it along.)
So you could do, for example:
class PhysicsForm(Form):
speed = TextField('Speed', description={'unit': 'meters/sec'})
distance = TextField('Distance', description={'unit': 'kilometers'})
Then you could use it in a jinja-style template something like:
{{ form.speed }} <label>{{ form.speed.description.unit }}</label>
footnote There was no real reason for using a dictionary as the value of description - it was merely to illustrate that you can put nearly any value in there, including containers which can hold many values.

How to filter special characters in django template

I am trying to display ValuesQuerySet list to drop down list in django template page. I jus to filter special characters while displaying in drop down. I tried autoescape syntax but it doesn't work. Is anyother way to do this.
in views.py:
email_accounts = EmailAccount.objects.filter(user__user=self.request.user).values()
form.fields['account'].queryset = email_accounts.values_list('a_email')
Here the value should like [{'a_email': u'xx#gmail.com'}, {'a_email': u'yy#gmail.com'}, {'a_email': u'zzz#gmail.com'}].
In template page
{{ form.account }}
So it displayed like below in drop down list
(u'xx#gmail.com')
(u'yy#gmail.com')
(u'zz#gmail.com')
I need to remove (u') those special chars when displaying in to drop down list. How to do that? any one suggest me.
You shouldn't be using a ValuesQueryset at all here. The queryset parameter for a ModelChoiceField expects, not surprisingly, a standard queryset.
email_accounts = EmailAccount.objects.filter(user__user=self.request.user)
form.fields['account'].queryset = email_accounts

Change empty_label for Django forms

I'm having some problems in changing the empty label for a form. I've read the documentation https://docs.djangoproject.com/en/dev/ref/forms/fields/#django.forms.ModelChoiceField and some pages here on stackoverflow, but I still don't get it. In my models, I have a tuple of countiescounty_choices = (('county1', _('county1')),('county2', _('county2')),('county3', _('county3')))
and the modelcounty = models.CharField(max_length=30, blank=True, default='n/a',choices=county_choices,verbose_name=_('county'), help_text=_('County')) I want to be able to override the 9 '-' empty label with a string in my own choice and to be able to translate it. So I imported everything from models and tried in forms county = forms.ModelChoiceField(queryset=Users.objects.all(),empty_label=_("my_own_choice")) and it does not work. I send it the {{ form.county }} variable in my html, but after my own choice, I get a wierd string 'sdadsada dsadadas' instead of my list of counties. Can you help me with that? Do I need to put the tuple with counties within a queryset? What if I don't want to send it a queryset at all?
Create a model for the counties and syncdb. This way you can manipulate and access them with a queryset, same way you do with Users. i.e. queryset=Counties.objects.all().
Look at the ModelChoiceField documentation for more help.

Django Template Slice - Reversing Order

Thanks to a very helpful hint from another question I learned I can limit the amount of values in a list by slicing it in the template as such:
{% for comment in thread.comment_set.all|slice:":3" %}
Now I would like to get the last 3 results of my comments so I figured a simple ":-3" or "-3" would do the trick, alas:
Caught an exception while rendering: Negative indexing is not supported.
Also using:
{% for comment in thread.comment_set.all|slice:":3" reversed %}
Does not do the trick because if I have 5 comments, instead of 1,2,3 it displays the first three in 3,2,1 order.
Is there any way I can display the last 3 comments of a post without going into my database? I'd love to be able to do this purely using the templating system.
SOLUTION
{% for comment in thread.comment_set.all|dictsortreversed:"created"|slice:"3" %}
Displays the last three thanks to my table having the created timestamp.
Django's database queries are evaluated lazily, so the result of thread.comment_set.all is a QuerySet, not a list. A QuerySet supports many list-like functions, but not negative slicing, so the indexing error is not coming from the template filter itself. (If you're curious, slices on QuerySet objects get translated into a limit clause on the SQL statement, which is why you can't use a negative number).
In general, Django encourages a strict decoupling of templates and models; the views.py module is the glue where you do any work that requires knowledge of database models and queryset methods to translate your model data into simple variables and structures for the template.
Running a related query on a model from a template is not something you typically see in a Django template, and there's a good reason for this. Right now, it may seem very simple to slice the last three elements from the comment_set. Keep in mind, though, that the database will not return results in any guaranteed order. This means that, in addition to your slice, you now also need to add an order_by clause; there's simply no way to express this in a template, nor should there be. Better to think of the view as the translation between your model and the template, and let such database-facing work be done there, rather than embedded in HTML.
In this case, I would encourage you to pass an ordered slice to your template from the view:
# take first three sorted descending
comments = thread.comment_set.order_by('-something')[:3]
context = Context({'comments':comments})
return HttpResponse(tmplt.render(context))
If you must do the slicing in the template, and you really don't care about sorting the results, pass a list to the template. The slice filter will happily do negative slicing:
comments = list(thread.comment_set.all())
context = Context('comments':comments)
In the template:
{% for comment in comments|slice:"-3:" %}
I haven't seen the dictsortreversed filter used too often, and according to the docs it takes a key to sort by
{% for comment in thread.comment_set.all|dictsortreversed:"name"|slice:"3" %}
Use the "ordering" attribute of Meta class of the Comment class to set the required ordering of elements.
IMO templates is not the proper place to order your dataset. Ordering should be done in either Models or Views.
I needed just this and made a more complete solution, I think. Hope people find it useful. The view needs to find the id of the last inserted record (example here). You have to reverse order and get the id (PK) of the newest last record entered which will now be the first record, easy to find now at the top of the heap ;-). Calculate the lower limit id value you desire, then sort from the lower to the newest or last entry using this operator at the end ...[n:m] (I think that is just a list operator), dropping the unwanted records, and then present it in normal order. For a django tables example here is my code:
def showLatestRels(request):
#This view shows the latest 15 entries in the Release table
LastInserted = Release.objects.order_by('-pk')[0]
UpperLimit = LastInserted.id
LowerLimit = UpperLimit - 15
RelTable = Release.objects.all()
tablevalslat = ReleaseTable(RelTable[LowerLimit:UpperLimit])
RequestConfig(request).configure(tablevalslat)
return render(request, 'DCESrtap/latrels.html', {'tablevalslat': tablevalslat}
)
Lots of ways to do it, see Django docs Make/limit Queries
But couldn't get the dictsort idea to work....
Can't you just slice the list before passing it to your template?
You can reverse a queryset if it is already ordered by "created". So here is a faster solution.
{% for comment in thread.comment_set.all.reverse|slice:":3" %}
Also if you don't want in reversed order.
{% for comment in thread.comment_set.all.reverse|slice:":3"|dictsort:"created" %}
Although you should follow Jarrets Hardie advice above and do all that logic in the views, rather then in the templates.

Categories

Resources