Automatically update user_id and date fields at django into database - django

I'm new to Python and Django and there are some things I would like to achieve in models.py:
After a user submits a form I would like to safe both the current date and the current user_id of the user into the database.
I know django offers to use #property decorators for those, but the problem with that is that I would like to make SQL queries using user_id and that doesn't work with decorators.
Another related question is how to establish calc fields like an automatic calculation of two values in the form before submitting.

Try making a model for the submission of the form. And give the model a foreign key which is connected to the user. Then add a DateField and prove the default value as datetime.now() from the datetime module which is probably already installed on your device.

In settings.py I added this line: 'crum.CurrentRequestUserMiddleware',
In models.py:
from django.contrib.auth.models import User
import crum
class PlantSpecies(models.Model):
def save(self, *args, **kwargs):
userid = crum.get_current_user()
self.userid = userid.id
super(PlantSpecies, self).save(*args, **kwargs)
userid = models.CharField(max_length=45, blank=True, default=crum.get_current_user())
date = models.DateTimeField(auto_now_add=True)
species = models.CharField(max_length=45, blank=True)
common_name = models.CharField(max_length=30, null=True, blank=True)
def __str__(self):
return self.species

Related

Update datetimefiled of all related models when model is updated

I have two models (Post and Display). Both have Datetime-auto fields. My problem is that i want to update all display objects related to a post, once a post is updated.
I have read here that you could override one models save method, but all the examples are About updating the model with the foreign key in it and then call the save method of the other model. In my case it's the other way arround. How can i do this ?
class Post(models.Model):
title = models.CharField(max_length=40)
content = models.TextField(max_length=300)
date_posted = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
rooms = models.ManyToManyField(Room, related_name='roomposts', through='Display')
def __str__(self):
return self.title
def get_absolute_url(self):
return "/post/{}/".format(self.pk)
class Display(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
room = models.ForeignKey(Room, on_delete=models.CASCADE)
isdisplayed = models.BooleanField(default=0)
date_posted = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.isdisplayed)
i want to update the date_posted of all related Display-objects once their related post is changed. I do not know if overriding the save-method works here.
in this case you should have a look at django's reverse foreign key documentation
https://docs.djangoproject.com/en/2.2/topics/db/queries/#following-relationships-backward
in your case you can override the save method on your Post model
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
#either: this excutes many sql statments
for display in self.display_set.all():
display.save()
#or faster: this excute only one sql statements,
#but note that this does not call Display.save
self.display_set.all().update(date_posted=self.date_posted)
The name display_set can be changed using the related_name option
in Display, you can change it:
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='displays')
Then, instead of using self.display_set in your code, you can use self.displays
Overriding save method works, but that's not were you should go, imo.
What you need is signals:
#receiver(post_save, sender=Post)
def update_displays_on_post_save(sender, instance, **kwargs):
if kwargs.get('created') is False: # This means you have updated the post
# do smth with instance.display_set
Usually it goes into signals.py.
Also you need to include this in you AppConfig
def ready(self):
from . import signals # noqa

Creating a unique object using DateField()

I would just like to create a model that essentially prevents the same date being selected by two different users (or the same user).
E.g if User1 has selected 2019-01-10 as a "date" for a booking, then User2 (or any other Users) are not able to create an object with that same date.
I have created a very basic model that can allow different Users to create an object using the DateField(). Using the Django admin page, I can create different instances of objects by two different Users (admin and Test_User).
In order to try to ensure that a new object can't be created if that date has already been used by a different object I have tried the following approach:
a compare function that utilizes __dict__.
models.py
from __future__ import unicode_literals
from django.db import models, IntegrityError
from django.db.models import Q
from django.contrib.auth.models import User
from datetime import datetime
class Booking(models.Model):
date = models.DateField(null=False, blank=False)
booked_at = models.DateTimeField(auto_now_add=True)
booking_last_modified = models.DateTimeField(auto_now=True)
class PersonalBooking(Booking):
user = models.ForeignKey(User, on_delete=models.CASCADE)
def compare(self, obj):
excluded_keys = 'booked_at', '_state', 'booking_last_modified', 'user',
return self._compare(self, obj, excluded_keys)
def _compare(self, obj1, obj2, excluded_keys):
d1, d2 = obj1.__dict__, obj2.__dict__
for k,v in d1.items():
if k in excluded_keys:
continue
try:
if v != d2[k]:
pass
except IntegrityError as error:
print(error)
print('Date already selected by different User. Please select another date.')
admin.py
from django.contrib import admin
from . import models
from .models import Booking, PersonalBooking
class PersonalBookingAdmin(admin.ModelAdmin):
list_display = ('format_date', 'user', )
def format_date(self, obj):
return obj.date.strftime('%d-%b-%Y')
format_date.admin_order_field = 'date'
format_date.short_description = 'Date'
def user(self, obj):
return obj.user()
user.admin_order_field = 'user'
user.short_description = 'User'
admin.site.register(models.PersonalBooking, PersonalBookingAdmin)
It didn't work as I had hoped, objects with the same date could still be created by the same or different users. Perhaps there is a simpler way? Or maybe I need to use the Q() class? I am not very familiar with it.
Any help is greatly appreciated.
Thanks.
You could do this validation at the database level by setting the unique attribute to True in your model's field.
class Booking(models.Model):
date = models.DateField(null=False, blank=False, unique=True)
booked_at = models.DateTimeField(auto_now_add=True)
booking_last_modified = models.DateTimeField(auto_now=True)
But this would present issues if the field was changed later to store time.
If you are going to be storing the time as well, you could override the model's default save function to check that there isn't another Booking with the same date (__date) each time it is saved. exists() returns True if there is a match, so this will throw a ValidationError if there is a match.
from django.core.exceptions import ValidationError
class Booking(models.Model):
date = models.DateTimeField(null=False, blank=False)
booked_at = models.DateTimeField(auto_now_add=True)
booking_last_modified = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
# Make sure there are no bookings on the same day
if Booking.objects.exclude(pk=self.pk).filter(date__date=self.date.date).exists():
raise ValidationError('There cannot be two bookings with the same date.')
super(Booking, self).save(*args, **kwargs)
Try this
https://docs.djangoproject.com/en/2.1/ref/models/fields/#unique-for-date
For user column set unique_for_date=True

django simple history is not working when changed from the application

I am using django-simple-history for recording the change history in database models. When I change a particular value from the model history is visible in the admin panel. But when value edited or changed from the application, the history is not visible in the admin panel. Is there something which I am missing.
from django.db import models
from simple_history.models import HistoricalRecords
class AcademicYear(models.Model):
academic_year_start = models.PositiveIntegerField(blank=False, null=False, max_length=4)
academic_year_end = models.PositiveIntegerField(blank=False, null=False, max_length=4)
history = HistoricalRecords()
def __unicode__(self):
return "%s-%s" % (self.academic_year_start, self.academic_year_end)
class Activity(models.Model):
activity_name = models.CharField(max_length=100, blank=False)
history = HistoricalRecords()
def __unicode__(self):
return "%s" % self.activity_name
The update
I investigated my
In the views instead of .save() I am using .update() for updating my model values.
activity_obj_list = Activity.objects.filter(activity_name=name)
activity_obj_list.update(activity_name=new_activity_name)
the update function does not create the history log. Is this some kind of bug ?
This isn't a bug, it's the expected behaviour because the .update() doesn't send the pre/post save signals. If you want the history to be triggered iterate through your queryset performing a save() rather than using the .update()
for obj in queryset:
obj.field1 = some_value
obj.save()

Limit Maximum Choices of ManyToManyField

I'm trying to limit the maximum amount of choices a model record can have in a ManyToManyField.
In this example there is a BlogSite that can be related to Regions. In this example I want to limit the BlogSite to only be able to have 3 regions.
This seems like something that would have been asked/answered before, but after a couple hours of poking around I haven't been able to find anything close. For this project, I'm using Django 1.3.
#models.py
class BlogSite(models.Model):
blog_owner = models.ForeignKey(User)
site_name = models.CharField(max_length=300)
region = models.ManyToManyField('Region', blank=True, null=True)
....
class Region(models.Model):
value = models.CharField(max_length=50)
display_value = models.CharField(max_length=60)
....
Any ideas?
You can override clean method on your BlogSite model
from django.core.exceptions import ValidationError
class BlogSite(models.Model):
blog_owner = models.ForeignKey(User)
site_name = models.CharField(max_length=300)
regions = models.ManyToManyField('Region', blank=True, null=True)
def clean(self, *args, **kwargs):
if self.regions.count() > 3:
raise ValidationError("You can't assign more than three regions")
super(BlogSite, self).clean(*args, **kwargs)
#This will not work cause m2m fields are saved after the model is saved
And if you use django's ModelForm then this error will appear in form's non_field_errors.
EDIT:
M2m fields are saved after the model is saved, so the code above will not work, the correct way you can use m2m_changed signal:
from django.db.models.signals import m2m_changed
from django.core.exceptions import ValidationError
def regions_changed(sender, **kwargs):
if kwargs['instance'].regions.count() > 3:
raise ValidationError("You can't assign more than three regions")
m2m_changed.connect(regions_changed, sender=BlogSite.regions.through)
Give it a try it worked for me.
Working! I have used this and its working properly.
Validation required before saving the data. So you can use code in form
class BlogSiteForm(forms.ModelForm):
def clean_regions(self):
regions = self.cleaned_data['regions']
if len(regions) > 3:
raise forms.ValidationError('You can add maximum 3 regions')
return regions
class Meta:
model = BlogSite
fields = '__all__'

count number of logins by specific user django?

Is their any way of counting number of django logins?
The last_login field of the auth_user gets updated with each login.
Can we make use of that field to count number of logins by a specific user?
There's also a 'user_logged_in' signal that'll do the trick with out the need to check for last logins etc.
class UserLogin(models.Model):
"""Represent users' logins, one per record"""
user = models.ForeignKey(user)
timestamp = models.DateTimeField()
from django.contrib.auth.signals import user_logged_in
def update_user_login(sender, user, **kwargs):
user.userlogin_set.create(timestamp=timezone.now())
user.save()
user_logged_in.connect(update_user_login)
Yes, in a sense. You'll need either a field in you app's UserProfile model to hold number of logins or a separate model for storing full login history. Then add signal handlers for last_login updates and record them in a model of your choice. Here's my example:
from django.db import models, signals
from django.contrib.auth.models import User
class UserLogin(models.Model):
"""Represent users' logins, one per record"""
user = models.ForeignKey(user)
timestamp = models.DateTimeField()
def user_presave(sender, instance, **kwargs):
if instance.last_login:
old = instance.__class__.objects.get(pk=instance.pk)
if instance.last_login != old.last_login:
instance.userlogin_set.create(timestamp=instance.last_login)
signals.pre_save.connect(user_presave, sender=User)
This is how I did (using django 1.7), similar to Guy Bowden answer:
from django.contrib.auth.models import User
from django.contrib.auth.signals import user_logged_in
class LoginUpdate(models.Model):
date_updated = models.DateTimeField(auto_now_add=True, null=True)
action_type = models.CharField(max_length=5)
action_user = models.ForeignKey(User, null=True, blank=True)
def update_user_login(sender, **kwargs):
user = kwargs.pop('user', None)
LoginUpdate.objects.create(action_type="Login", action_user=user)
user_logged_in.connect(update_user_login, sender=User)
Later you can count number of logins, in your views.
You can use django-timeStamps to store rcent login times. A simple light weight tool.