When to use __str___ method inside Django model? - python

I have created a Django model with the following attributes.
class Info(models.Model):
number = models.IntegerField()
ID = models.IntegerField()
reading = models.IntegerField()
date = models.DateField()
I would like to make it so that when a user searches for an 'ID' or 'number' from the database, they are shown the date and reading. Here is my search results code in views.py:
class SearchResultsView(ListView):
model = Info
template_name = 'search_results.html'
def get_queryset(self):
query = self.request.GET.get('q')
reading_list = Info.objects.filter(
Q(ID__icontains=query) | Q(number__icontains=query)
)
return reading_list
And here is my search_results.html template:
<h1>Search Results</h1>
<ul>
{% for reading in reading_list %}
<li>
{{ reading.reading }}, {{ reading.date }}
</li>
{% endfor %}
</ul>
I am a little confused as to whether I should include a 'str' method in my model. Will the app be able to print the date and reading using just this code?

Based on your current settings, there is no need to add a __str__ function in your model because you are using instance's fields rather than instance itself.
However, if there are any references to the instance itself, e.g. a foreign key to this model or you just want to check the instance itself, adding a __str__ function will increase the readability. You can check the __str__ documentation for details.
Without __str__ field, if you have an Info instance, you will have:
<Info: Info object (1)>. After adding a __str___ function and return str(id) for example, you will see <Info: 1>.
It would be great to have a CharField(e.g. description = models.CharField()) in your Info model if you want to add the __str__ function. Then the representation of this object would be <Info: Good Reading>

Whenever an instance of model is created in Django, it displays the object as ModelName Object(1).to make changes to your Django model using this
def __str__(self):
return str(self.id) #if its integer, make it str
return self.name #if its already str. you dont make it str
it changes the display name from ModelName Object(1) to field name of def __str__(self): in your admin panel.
and one thing def __str__(self) for python 3 and def __unicode__(self): for python 2

Related

Multiple foreign key lookups

I have the following models in my app
Account
class Account(CommonModel): # Accounts received from Client
client = models.ForeignKey('Client', on_delete=models.RESTRICT)
reference = models.CharField(db_index=True, max_length=50)
def __str__(self):
return f"{self.client} {self.reference}"
Person
class Person(CommonModel):
title = models.CharField(max_length=100,choices=choi.person_title())
name = models.CharField(db_index=True, max_length=100)
birth_date = models.DateField()
def __str__(self):
return f"{self.title} {self.name}"
AccountPerson
class AccountPerson(CommonModel): # Account -> Person link
account = models.ForeignKey("core.Account", on_delete=models.RESTRICT, related_name="accountperson_account")
person = models.ForeignKey("core.Person", on_delete=models.RESTRICT, related_name="accountperson_person")
contact_type = models.CharField(max_length=50, choices=choi.contact_type())
def __str__(self):
return f"{self.account} - {self.person} ({self.contact_type})"
The AccountPerson model holds relationships between accounts and people (one person can have multiple accounts). I'm trying to return a query set containing a list of Accounts, and the Person they're linked to (if any). My background is SQL, so I'm thinking of a query that would hit Account -> AccountPerson --> Person, but I'm stuck.
I've tried prefetch_related() but I'm only returning details in the Account table - I'm unsure of how to access Person from there and put those fields into my HTML file.
View
def account_list(request):
data = Account.objects.all().prefetch_related('accountperson_account')
return render(request, 'core/account_list.html', {'data': data})
account_list.html
Code condensed for readability
...
{% for i in data %}
<tr>
<td>{{i.client}}</td>
<td>{{i.reference}}</td>
{% endfor %}
...
I'm currently in a position where my page loads, and I see the entries in my Account model, but that's it.
Update
I changed my view to this
def account_list(request):
data = AccountPerson.objects.all().select_related('account').select_related('person')
return render(request, 'core/account_list.html', {'data': data})
And I can now access fields in Account and Person in my HTML like so
{% for i in data %}
<tr>
<td>{{i.account.client}}</td>
<td>{{i.account.reference}}</td>
<td>{{i.contact_type}}</td>
<td>{{i.person.name}}</td>
{% endfor %}
I just want to check that this is the right way (or one of them)?
I'd change the datamodel slightly to be more Django-y. Django has the concept of ManyToMany fields which is what you're trying to accomplish. (https://docs.djangoproject.com/en/3.1/ref/models/fields/#django.db.models.ManyToManyField)
You would define the Person model as you did and change the Account model to have a ManyToMany field (you could also switch it around, that won't matter).
You can also defined the intermediate model like you intended. Use the through argument on the ManyToMany for this.
You can use Related Manager to handle all lookups both ways: account.person and person.account (the 'account' part is set by the related_name).
class Account(CommonModel): # Accounts received from Client
client = models.ForeignKey('Client', on_delete=models.RESTRICT)
reference = models.CharField(db_index=True, max_length=50)
person = models.ManyToManyField(Person, through=AccountPerson, related_name='account')

How do I avoid `obj.save()` not to update `updated_at` field?

The title alone seems to be vague. Here is the detail.
I have the following Model:
class Post(models.Model):
title = models.CharField(max_length=100)
# deleted for brevity
created_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
count_visits = models.PositiveIntegerField(default=1) # counts visit time of posts
and added the following code in blogs.html:
{% if post.created_at == post.updated_at %}
Posted {{ post.created_at|naturaltime }}
{% else %}
Updated {{ post.updated_at|naturaltime }}
{% endif %}
then I made a simple if statement in DetailView to keep track of number of visitors of a certain post:
def post_detail(request, title_slug):
obj = get_object_or_404(Post, slug=title_slug)
session_key = f'key_{title_slug}'
if not request.session.get(session_key, False):
obj.count_visits += 1
obj.save()
request.session[session_key] = True
return render(request, 'blogApp/detail.html', {'post': obj})
Here is the problem, When I create a post, in list view it shows Posted now.
but as I click the post to view its detail. It shows Updated now
and I think I know what is the problem, when I go to detail view. it creates a session_key, increments obj.count_visits += 1 and save the object obj.save().
When obj.save() is called it updates the database including updated_at field and that is why I get Updated now.
How do I solve this? I want to increment number of count_views field, but when object is saved it shouldn't change updated_at field.
I hope you help me. Thank You
You can do the update directly on the database, bypassing the Django layer:
from django.db.models import F
if not request.session.get(session_key, False):
type(obj).objects.filter(pk=obj.pk).update(
count_visits=F('count_visits') + 1
)
This will result in the following DB query:
UPDATE "<db_table>" SET "count_visits" = ("<db_table>"."count_visits" + 1) \
WHERE "<db_table>"."id" = <obj.id>
The F-object is used to resolve references to existing database objects, in this case, we're getting the current value of field count_visits. The important property of this is that it can generate expression from the operation and operands (objects) by implementing e.g. __add__, __sub__, __mul__, __div__ and other dunder methods (actually implemented by its superclass Combinable).
Another approach would be to take an additional keyword argument to save that indicates whether the updated_at field should be updated as well. To implement this, at first we need to drop the auto_now argument from the field instantiation as well:
from django.utils import timezone
class Post(models.Model):
...
updated_at = models.DateTimeField(default=timezone.now)
...
def save(self, *args, set_updated_at=True, **kwargs):
if self.pk is not None:
if set_updated_at:
self.updated_at = timezone.now()
super().save(*args, **kwargs)
Now you only need to pass the argument when calling save e.g. in this case, it should be False:
if not request.session.get(session_key, False):
obj.count_visits += 1
obj.save(set_updated_at=False)

Django view only returning incomplete data, only one field to template

I have forked the django-oscar catalogue app to alter the models being used. Not in a major way, and not in a way that would affect pulling data from the database as far as I can see. This seems to be supported by the fact the the django-oscar dashboard still works fine and lets me add and view products. My models.py from my forked app:
from django.db import models
class Collection(models.Model):
name = models.CharField(max_length=50)
prod_category = models.CharField(max_length=50)
description = models.TextField()
manufacturer = models.TextField()
num_products = models.PositiveIntegerField()
image_url = models.URLField()
from oscar.apps.catalogue.abstract_models import AbstractProduct
class Product(AbstractProduct):
collection = models.ForeignKey(Collection, on_delete=models.CASCADE, null=True)
multiplier = models.DecimalField(max_digits=2, decimal_places=1, default='2.2')
from oscar.apps.catalogue.models import *
Here is my relevant view from my views.py
def product(request):
template = loader.get_template('/home/my_app/my_site/main_page/templates/main_page/product.html')
prods = Product.objects.values_list('categories')
context={'prods': prods}
return HttpResponse(template.render(context))
I tried loading from the built in model and my forked model (commenting and uncommenting one or both), neither makes a difference:
#from forkedoscarapps.catalogue.models import Product
from oscar.core.loading import get_class, get_model
Product = get_model('catalogue', 'product')
And the code I am using in the template to display data from the view:
{% for instance in prods %}
<li><{{ instance.name }}</li>
{% endfor %}
The resulting HTML is:
<li></li>
Which shows it is reaching the for loop, but for some reason no data is returned.
There is at least one category called beds, which displays fine in the django-oscar dashboard. What have I missed in my view?
edit: When I change instance.name to just instance I get the following returned in the HTML:
(1,)
So it is somewhat working, and showing what I assume is the primary key being returned, but why is the name of the field not being returned?
Product.objects.values_list('categories') yields a list of id tuples that represent the categories associated with the products in that queryset. That's not what you want to send to the template, you want to send instances, more specifically product instances if I'm not mistaken.
Do Product.objects.all() instead, and just use {{ instance.title }} in the template according to the definition of the oscar model: https://github.com/django-oscar/django-oscar/blob/master/src/oscar/apps/catalogue/abstract_models.py and to what ever you customised over it.

Comparing two django variable in a django if block

I am having a tough time with following code.
{% if object.author == user.username %}
That code is not working neither giving error.
So I have articles app inside my django project. I want to make sure that if a user goes to their own post, then only they should be able to see delete and edit links (I will be placing them inside if block).
The Article model is as follows:
class Article(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('article_detail', args=[str(self.id)])
Here
{{user.username}}
{{object.author}}
both are valid django variables.
{{user.username}}
specifies username of current logged in user
{{object.author}}
specifies author of that particular post.
Please help me out to implement with comparison logic of both variables. I am using python 3.6, django 2.1 and django template language.
Your question would be easier to answer if you posted your models (and formatted your text correctly).
I presume that object.author is actually a ForeignKey to the User model. Therefore, you should compare directly with the user, not with the username attribute of the user:
{% if object.author == user %}

Accessing model attributes for each list component in Django

I am trying to pass a list of objects from my views.py to my html template but I can't access the objects model attributes at my HTML template. Please, check the code below:
class.py:
class FOF(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=200)
size = models.IntegerField()
pub_date = models.DateTimeField('date published')
view_count = models.IntegerField()
def __unicode__(self):
return self.name
class Frame(models.Model):
url = models.CharField(max_length=200)
fof = models.ForeignKey('FOF', related_name='frame_set')
index = models.IntegerField()
def __unicode__(self):
return self.url
views.py:
def my_fof(request, fof_name_value):
my_list = FOF.objects.all().order_by('-rank')
...
return render_to_response('uploader/my_fof.html', {'my_list':my_list}, context_instance=RequestContext(request))
I usually can access the attributes at the html file when I pass a single object but once I am passing a list of objects, it seems impossible to access those attributes.
For example, when I type alert({{ fof_list.0 }}); it prints the object attribute name, because it was defined in the models.py:
def __unicode__(self):
return self.name
But once I try to access any of its attributes, as pub_date: alert({{ fof_list.0.pub_date }}); it returns undefined, as that attribute is unavailable or unreachable.
Any Idea on how to proceed to reach those attributes when passing an entire list? Or how to proper pass that list?
Cheers.
Use the for template tag to iterate over an iterable and get each element in turn.
You first need to place the content of attribute in a javascript variable.
<script>
var pub_date = '{{my_list.0.pub_date}}';
alert(pub_date);
//this will also work
alert('{{my_list.0.pub_date}}') # enclosed in single or double quotes
</script>
Use a for loop and iterate it to get each object in a template.
{% for obj in my_list %}
<span>{{obj.pub_date}}</span>
<span>{{obj.size}}</span>
---
{% endfor %}

Categories

Resources