Django form save or model save with a select field? - django

So I'm still new to Django. I have a single field in my form. And I was just wondering whether or not I need a form save function or a model save function? When is it appropriate to use either or?
For instance: My form:
class OpinionStatusForm(forms.Form):
choices = (('0', "Your Status"), ('1', "This"), ('2', "That"), ('3', "The Other"))
status = forms.CharField(max_length=2, widget=forms.Select(choices=choices, attrs={'class':'status_dropdown','onchange': 'this.form.submit();'}), required=False)
def save(self, opinion_status):
opinion_status.status = self.cleaned_data['status']
My model:
class OptionStatus(models.Model):
user = models.ForeignKey(User, null=True, unique=True)
status = models.CharField(max_length=2, choices=opinion_statuses)
opinion = models.ForeignKey(Opinion, null=True, blank=True)
def __unicode__(self):
return self.status
def save(self, *args, **kwargs):
super(OpinionStatus, self).save(*args, **kwargs)
I'm going to be ajax-ing the form. I don't know if that makes a difference or not. Thanks!

What you actually need, is a ModelForm. In your example you're working with a standard forms.Form. This is not bound to a model instance. As a result, there's also no need for a save method. The best examples are really given inside the Django docs:
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/
Go step by step over the code examples and you'll understand. It would be too much to explain it all in one Stackoverflow answer - and the Django docs are incredible thorough.

Related

How to update model field after saving instance?

I have a Django model Article and after saving an instance, I want to find the five most common words (seedwords) of that article and save the article in a different model Group, based on the seedwords.
The problem is that I want the Group to be dependent on the instances of Article, i.e. every time an Article is saved, I want Django to automatically check all existing groups and if there is no good fit, create a new Group.
class Article(models.Model):
seedword_group = models.ForeignKey("Group", null=True, blank=True)
def update_seedword_group(self):
objects = News.objects.all()
seedword_group = *some_other_function*
return seedword_group
def save(self, *args, **kwargs):
self.update_seedword_group()
super().save(*args, **kwargs)
class Group(models.Model):
*some_fields*
I have tried with signals but couldn't work it out and I'm starting to think I might have misunderstood something basic.
So, to paraphrase:
I want to save an instance of a model A.
Upon saving, I want to create or update an existing model B depending on A via ForeignKey.
Honestly I couldn't understand the rationale behind your need but I guess below code may help:
def update_seedword_group(content):
objects = News.objects.all()
"""
process word count algorithm and get related group
or create a new group
"""
if found:
seedword_group = "*some_other_function*"
else:
seedword_group = Group(name="newGroup")
seedword_group.save()
return seedword_group
class Group(models.Model):
*some_fields*
class Article(models.Model):
seedword_group = models.ForeignKey("Group", null=True, blank=True)
content = models.TextField()
def save(self):
self.group = update_seedword_group(self.content)
super().save()

Django - Keep latest record of User

I want to keep the latest record of a user:
class Record(models.Model):
name = models.ForeignKey(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='site_pics')
date = models.DateTimeField(null=True, blank=True, auto_now_add=True)
def save(self, *args, **kwargs):
super(Record, self).save(*args, **kwargs)
numdata = Record.objects.select_related('name').count()
print(numdata)
#if numdata > 1:
# Record.objects.select_related('name').first().delete()
As per this post Filter latest record in Django, I tried:
.distinct()
.select_related()
.prefetch_related()
None of them return the correct number of records or don't work at all because I'm using SQLite.
Thank you for any suggestions
In that case it might be better to change the modeling to a OneToOneField, such that the database will reject a second Record for the same user. You might also want to change the name of the relation to user, since you are referring to a user object, not its name.
In the save(…) method, you can look if the primary key is not yet filled in, if that is the case, you can delete the original record of the user. Regardless whether that exists or not. If this records does not exist, then it will act as no-op, where nothing changes in the database:
class Record(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='site_pics')
date = models.DateTimeField(null=True, blank=True, auto_now_add=True)
def save(self, *args, force_insert=False, **kwargs):
if self.pk is None or force_insert:
Record.objects.filter(user_id=self.user_id).delete()
return super().save(*args, force_insert=force_insert, **kwargs)
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.

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

Django - Update integer field value in one model by change in other

Here is a project I've created to practice, in my models.py,
class Post(models.Model):
title = models.CharField(max_length = 140)
author = models.ForeignKey(User, on_delete=models.CASCADE)
votes = models.BigIntegerField(default=0, blank=True)
class Vote(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='voter')
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='vpost')
#receiver(post_save, sender=Vote)
def update_votes(sender, **kwargs):
# # ??
Here I have a Voteform with that user can vote any particular post. That part works well.
Here is my question, whenever a user votes a particular post, I want votes field in Post model to increase as well.
I know I can show it with {{post.vpost.count}} in my html. But I want that increment here.
Other way I have tried,
class Vote(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='voter')
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='vpost')
def update_votes(self):
p = self.post
p.votes += 1
p.save()
This one only works once, not working from second time, so I want to use signal method. So how can I update the vote field in Post model using signal?
Nearly there. I would rename Post.votes to Post.votes_count as votes indicates a reverse relationship.
#receiver(post_save, sender=Vote)
def update_votes(sender, instance, **kwargs):
post = instance.post
post.votes_count += 1
post.save()
Although you might want to make sure that the count is correct, by introducing another query:
#receiver(post_save, sender=Vote)
def update_votes(sender, instance, **kwargs):
post = instance.post
post.votes_count = post.votes_set.all().count()
post.save()
You might also want to do this when/if a Vote is deleted to make sure the count is correct.
Bear in mind you could also just do this in the Vote's save method instead of needing signals.
You could also do this as a cronjob or task depending on your circumstances

M2M using through and form with multiple checkboxes

I'd like to create a form allowing me to assign services to supplier from these models. There is no M2M relationship defined since I use a DB used by others program, so it seems not possible to change it. I might be wrong with that too.
class Service(models.Model):
name = models.CharField(max_length=30L, blank=True)
class ServiceUser(models.Model):
service = models.ForeignKey(Service, null=False, blank=False)
contact = models.ForeignKey(Contact, null=False, blank=False)
class SupplierPrice(models.Model):
service_user = models.ForeignKey('ServiceUser')
price_type = models.IntegerField(choices=PRICE_TYPES)
price = models.DecimalField(max_digits=10, decimal_places=4)
I've created this form:
class SupplierServiceForm(ModelForm):
class Meta:
services = ModelMultipleChoiceField(queryset=Service.objects.all())
model = ServiceUser
widgets = {
'service': CheckboxSelectMultiple(),
'contact': HiddenInput(),
}
Here is the view I started to work on without any success:
class SupplierServiceUpdateView(FormActionMixin, TemplateView):
def get_context_data(self, **kwargs):
supplier = Contact.objects.get(pk=self.kwargs.get('pk'))
service_user = ServiceUser.objects.filter(contact=supplier)
form = SupplierServiceForm(instance=service_user)
return {'form': form}
I have the feeling that something is wrong in the way I'm trying to do it. I have a correct form displayed but it is not instantiated with the contact and checkboxes aren't checked even if a supplier has already some entries in service_user.
You are defining services inside your Meta class. Put it outside, right after the beginning of SupplierServiceForm. At the very least it should show up then.
Edit:
I misunderstood your objective. It seems you want to show a multiple select for a field that can only have 1 value. Your service field will not be able to store the multiple services.
So, by definition, your ServiceUser can have only one Service.
If you don't want to modify the database because of other apps using it, you can create another field with a many to many relationship to Service. That could cause conflicts with other parts of your apps using the old field, but without modifying the relationship i don't see another way.
The solution to my problem was indeed to redefine my models in oder to integrate the m2m relationship that was missing, using the through argument. Then I had to adapt a form with a special init method to have all selected services displayed in checkboxes, and a special save() method to save the form using m2m relationship.
class Supplier(Contact):
services = models.ManyToManyField('Service', through='SupplierPrice')
class Service(models.Model):
name = models.CharField(max_length=30L, blank=True)
class ServiceUser(models.Model):
service = models.ForeignKey(Service, null=False, blank=False)
supplier = models.ForeignKey(Supplier, null=False, blank=False)
price = models.Decimal(max_digits=10, decimal_places=2, default=0)
And the form, adapted from the very famous post about toppings and pizza stuff.
class SupplierServiceForm(ModelForm):
class Meta:
model = Supplier
fields = ('services',)
widgets = {
'services': CheckboxSelectMultiple(),
'contact_ptr_id': HiddenInput(),
}
services = ModelMultipleChoiceField(queryset=Service.objects.all(), required=False)
def __init__(self, *args, **kwargs):
# Here kwargs should contain an instance of Supplier
if 'instance' in kwargs:
# We get the 'initial' keyword argument or initialize it
# as a dict if it didn't exist.
initial = kwargs.setdefault('initial', {})
# The widget for a ModelMultipleChoiceField expects
# a list of primary key for the selected data (checked boxes).
initial['services'] = [s.pk for s in kwargs['instance'].services.all()]
ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
supplier = ModelForm.save(self, False)
# Prepare a 'save_m2m' method for the form,
def save_m2m():
new_services = self.cleaned_data['services']
old_services = supplier.services.all()
for service in old_services:
if service not in new_services:
service.delete()
for service in new_services:
if service not in old_services:
SupplierPrice.objects.create(supplier=supplier, service=service)
self.save_m2m = save_m2m
# Do we need to save all changes now?
if commit:
self.save_m2m()
return supplier
This changed my first models and will make a mess in my old DB but at least it works.