Django: How to Convert QuerySet into String - python

I'm using Python 3 and trying to convert a QuerySet into human-readable text. I have a line like this:
top_post = Post.objects.filter(category='1')[:1]
That prints like this:
<QuerySet [<Post: Test Post 1>]>
What makes me scratch my head is a similar QuerySet successfully converts when displayed via a template:
latest = Post.objects.order_by('-published_date')[:5]
"Latest" uses a for...loop in the template:
{% for latest_posts in latest %}
<h1>{{ latest_posts }}</h1>
While "top_post" displays only a blank:
<h1>{{ top_post }}</h1>
Anyone see what's missing?

{{top_post}} is a query set (slicing a queryset also produces a queryset), so it should display that. When you loop over the queryset, as you do over latest, you display the objects inside.
{{top_post.0}} would give you the object top_post contains.
Alternatively you can use
top_post = Post.objects.filter(category='1').first()
to get the object directly.

You should implement the __str__ method in the Post model where you define what to print.
Moreover you should .get instead of .filter if you expect one object to be returned.

to solve it you can use nested loops (A loop in other loop)

Convert it into list
def
return(Post.objects.filter(category='1').first)

If you are using filter then I am assuming you want more than one value else use the get method.
You can convert the queryset into a list and then play around the list.
top_post = list(Post.objects.filter(category='1')[:1])[0]
Now top_post will give the first object in the list.

Related

Can I use a variable as index in for loop in python?

I have this for loop on my python template to fulfill an array of values:
labels: [{% for bftimes in dataBF.buffers.0.times %} "{{ bftimes }}", {% endfor %}]
I would like to know if I can use an int variable as an index instead writing it directly, as seen on the code above.
I need to use the index of the selected value of a dropdown:
//returns the index of the selected value
document.getElementById("buffer").selectedIndex
The question needs more context to help us understand your goal. It seems you are mixing python with javascript data structures.
My general recommendation is that you first prepare the python data structure to what you will need and then convert it to json. Looping within Django template language should be used only on simple cases.
If you use django>=2.1 you can use json-script template tag
{{ data|json_script:"my-data" }}
https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#json-script
If not, you can use
# views.py
import json
def foo(request):
dataBF = {"a": [{"c": [1,2,3,1,1]},{"d": [1,2,3,1,1]}]}
# here you can manipulate the data accordingly to what you need
data = json.dumps(dataBF)
return render(request, "index.html", context={"data": data})
On the template side
<script>
const datajs = {{ data | safe }};
</script>
The datajs is a javascript object that you can work with.
I've made an example that you can check https://repl.it/#IvanPereira/python-to-javascript-django-template
You can do this in 2 ways.
Store all values in a list, which JavaScript will consider as json array, and loop over using JavaScript itself. This way you won't be able to update records from server and all values that has to be looped over should be pre-fetched.
You can use AJAX call to pass the selected index from JavaScript and return the new array and update that in the template using JavaScript itself.

How to access a special field in a subquery?

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.

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

Python / Django - Form Selection Value is Object Id... Can't Get Object

I have a form select box that has values corresponding to objects. The user selects an input and the form submits. I then want to take that value and retrieve the object that has an ID equivalent to the selected form value. However, when I try to do so, I'm getting an error like so:
int() argument must be a string or a number, not 'Cars'
Example:
if form.is_valid():
car = Car.objects.get(id=form.cleaned_data['id'])
I'm guessing the problem is that the returned value is a string and NOT an integer. In PHP this is SIMPLE. How do I typecast or use the returned form value to get an associated object?
Seems as though Django is not returning the value of the form element, but instead the user visible option name...
Form Class:
class CarForm(forms.ModelForm):
class Meta:
model = Car
Html Form:
<form action="">
<select name="id">
{% for car in cars %}
<option value="{{car.id}}">{{car.title}}</option>
{% endfor %}
</select>
</form>
The id field has to be an integer, or a string that can be converted to an integer.
Simple as that!
Somehow, your form.cleaned_data['id'] is returning a model called Cars
Ensure it returns a number if you want to pass it into get(id=
You can use ModelChoiceField instead of generating the select using HTML
in forms.py:
class CarSelectForm(forms.Form):
car = forms.ModelChoiceField(queryset=Car.objects.all(), empty_label=None)
in view.py:
if form.is_valid():
car = form.cleaned_data['car']
This is maybe a bad, but I think working answer. If anyone has a real solution, please post because I still need it.
(I'm using a modelformset, but just a modelform may work the same)
For me, the {{ form.id }} works on the page (puts the id) and comes back correctly in the POST data. However, somewhere along the line it gets converted (as Yuji said in his post) into the model object represented by that id and that is what is in cleaned_data.
In short, change
car = Car.objects.get(id=form.cleaned_data['id'])
to
car = form.cleaned_data['id']
I think it just looks like a string just because when you print or debug, it's using your str or unicode representation.
Repeat: This is almost certainly a side effect or bad way to do things. If I figure out a better way, I'll post it.
I have the same issue... so I tried the following:
ts_1 = form.cleaned_data['lista_trabajos']
ts_2 = request.POST['lista_trabajos']
print(ts_1) # this returns what user sees, ex: 1 - folders
print(ts_2) # this returns value from that option, in my case: 1
Unfortunately, I have been reading that by using raw POST data is not recommended. At the moment, I cannot figure out how to validate and get the raw POST data by using something similar to "clean_data".
You can read about this in: https://books.google.com.ar/books?id=8sU7DwAAQBAJ&pg=PA229&lpg=PA229&dq=form+cleaned_data+to+get+raw+post+data&source=bl&ots=RN9WKRaGJs&sig=QpSoPdI9YSHSNk0zAQIO8phSbOw&hl=es&sa=X&ved=0ahUKEwiBouHattnaAhULFZAKHUKmA4QQ6AEIRzAD#v=onepage&q=form%20cleaned_data%20to%20get%20raw%20post%20data&f=false

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