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

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)

Related

Multiple search with django foreign key (Related Field got invalid lookup: icontains)

I understand that you can't directly use icontains on a foreign key when searching but I haven't found a solution yet.
Here is my search view in views.py (I have imported every model needed):
def search(request):
# if the user actually fills up the form
if request.method == "POST":
searched = request.POST['searched']
# author__icontains part is not working
posts = Post.objects.filter(Q(title__icontains=searched) | Q(author__author__icontains=searched))
return render(request, 'blog/search.html', {'searched': searched, 'posts': posts})
else:
return render(request, 'blog/search.html', {})
Here is my model in model.py:
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
Mainly, this is not working:
posts = Post.objects.filter(Q(title__icontains=searched) | Q(author__author__icontains=searched))
The error is Related Field got invalid lookup: icontains
author is a User object. Therefore you should work with username, or first_name, or some other field. Likely author is also the value of a related_name=… [Django-doc] that thus makes a LEFT OUTER JOIN on another table, and thus would work on the primary key(s) of that table.
You thus filter with:
def search(request):
# if the user actually fills up the form
if request.method == 'POST':
searched = request.POST['searched']
# author__icontains part is not working
posts = Post.objects.filter(
Q(title__icontains=searched) |
Q(author__username__icontains=searched)
)
return render(request, 'blog/search.html', {'searched': searched, 'posts': posts})
return render(request, 'blog/search.html')
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: Searching is usually done through a GET rquest, since that means the query is stored in the querystring and thus the URL. This makes it convenient to for example share the URL with the query to someone else, or bookmark the result. POST requests are usually used for state-changing actions, or for requests with sensitive data.
This problem is caused by Django's inability to handle foreign keys with multiple values for a single field. The reason for this limitation is that Django doesn't know how to resolve these conflicts, so it simply ignores them. In your case, since there are two fields in your model that match the search criteria, Django will ignore both of those results and display an empty list.
To fix this issue, we need to add a new attribute to our model called "icontains" which would contain the value of the other field. Then, we'll set this attribute as a default value for the "author" field when querying from the database. Here is what your model should look like now:
class Post(models.Model): title = models.CharField(max_length=100) content = models.TextField() date_posted = models.DateTimeField(default=timezone.now) author = models.ForeignKey(User, on_delete=models.CASCADE) icontains = models.CharField(max_length=100, null=True, blank=True) def __str__(self): return self.title def get_absolute_url(self): return reverse('post-detail', kwargs=dict(pk=self.pk))
With this change, the code will work properly.
For more information about this limitation, see the Django documentation here: https://docs.djangoproject.com/en/1.9/topics/db/queries/#lookups-that-span-relationships

Django - Retrieve or get data from selected foreign key field

Relatively new to Django, I'm working on a Django project and attempting to retrieve particular foreign key object into variable when it's selected in Form.
model.py
class item_category(models.Model):
idItemCat = models.CharField(primary_key=True max_length=5)
nameCategory = models.CharField(max_length=150)
def __str__(self):
return self.nameCategory
class item_code(models.Model):
idItemCat = models.ForeignKey(item_category, on_delete=models.DO_NOTHING)
idItemCode = models.CharField(primary_key=True, editable=False, max_length=20)
def __str__(self):
return self.idItemCode
I know I could retrieve object with making QuerySet such as .objects.last() and .objects.filter() or else, but it's just retrieve objects from database or existing data. What I'm about to do is, when a user submit a new data it'll retrieve particular foreign key object based on what I'm selected in this Form, so I could put into variable.
Any idea how should do it? it would be so much appreciated.
You really should look at the Django Docs on forms. They are excellent, and will teach you the right way to handle forms, a complicated topic.
To answer your question directly, it looks like you already have the html part, and as long as it has the form tag, like this:
<form action='your_view' method='post'>
...
</form>
Then in your view you could do something like this:
def your_view(request):
if request.method == 'POST':
category = item_category.objects.get(pk=request.POST.get('name_attribute_of_your_input'))
I'd need to see more specifics to give you a better answer, but there are several issues you should fix first.
First, your class names should be capitalized, field names lower case, and second,are you sure you want to make a CharField the primary key? You can, but for most cases, the automatically generated integer pk that Django creates is best.
class ItemCategory(models.Model):
# Django will create an integer pk for you
# idItemCat = models.CharField(primary_key=True max_length=5)
nameCategory = models.CharField(max_length=150)
def __str__(self):
return self.nameCategory
class ItemCode(models.Model):
idItemCat = models.ForeignKey(ItemCategory, on_delete=models.DO_NOTHING)
# Again, just let Django generate the primary key
# idItemCode = models.CharField(primary_key=True, editable=False, max_length=20)
def __str__(self):
return self.idItemCode

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')

When to use __str___ method inside Django model?

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

How to show balance ZERO by default when someone new makes an account?

I have a page which shows a user how much balance he/she has. I put that balance in admin page. But when somebody makes an account first time, I want to show them their balance zero. How to do it?
models.py
class Balance(models.Model):
amount = models.DecimalField(max_digits=12, decimal_places=2)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = 'Balance'
def __str__(self):
return f'{self.owner} Balance'
views.py
#login_required
def balance(request):
total = Balance.objects.get(owner=request.user)
return render(request, 'nextone/balance.html', {'amount': total.amount})
HTML page
<h2>Your Balance is Rs. {{amount}}</h2
I think first of all it might be worth to slightly alter your model, and use a OneToOneField [Django-doc] here. This is in essence a ForeignKey [Django-doc], but with a unique=True constraint, such that a user can have at most one Balance, not multiple ones. Furthermore it is advisable to use get_user_model [Django-doc] instead of a reference to the User class, since if you later change your user model, it will now refer to the new model. In fact using a custom user model with a balance might be a better modeling choice, since then there is never a "glitch" when a User exists, but a related Balance does not (yet) exists.
from django.contrib.auth import get_user_model
class Balance(models.Model):
amount = models.DecimalField(max_digits=12, decimal_places=2, default=0)
owner = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
class Meta:
verbose_name_plural = 'Balance'
def __str__(self):
return f'{self.owner} Balance'
In our view we can obtain the related balance with request.user.balance (this is due to the logic of a OneToOneField). Since that can, strictly speaking, fail, we might want to pass None in case it fails, like:
#login_required
def balance(request):
try:
balance = request.user.balance
except Balance.DoesNotExist:
balance = None
return render(request, 'nextone/balance.html', {'balance': balance})
Now we can render this with:
{% if balance.amount %}
<h2>Your Balance is Rs. {{ balance.amount }}</h2>
{% else %}
<h2>balance zero</h2>
{% endif %}
You can set a default value for fields, with the default keyword argument, like so.
amount = models.DecimalField(max_digits=12, decimal_places=2, default=0)
That will give the amount field a value of 0, whenever not explicitly set otherwise.
use attribute default=0 in your model field

Categories

Resources