Django: tracking if a field in the model is changed - python

I have a model:
class Object(Object1):
name = models.CharField(max_length=255, blank=True)
description = models.TextField(blank=True)
date_updated = models.DateTimeField(blank=True, null=True)
I want to track if there is anything inserted into the date_updated field, then create another object in another model, without overriding the save method.
For example:
if date_updated:
MyModel.objects.create(type="D", user=request.user)
Although I have tried this, but still no success.

You can use tracker field from django-model-utils.
Add a tracker to the model:
class Object(Model):
name = models.CharField(max_length=255, blank=True)
description = models.TextField(blank=True)
date_updated = models.DateTimeField(blank=True, null=True)
tracker = FieldTracker()
You can check in save() or in other places where you usually update the model:
if object.tracker.has_changed('date_updated'):
create_new_object(data)

Sorry, just noticed that you are referencing request.user. So this will NOT work for you. Since you need the particular request object, its probably best done in the view that the request object is referring to.
With pre_save you could compare the current instance's property value with the one currently in the database.
#receiver(pre_save, sender=MyModel)
def check_date(sender, instance=None, created=False, **kwargs):
changed = created or instance.date_updated != MyModel.objects.get(pk=instance.pk).date_updated
if changed:
MyModel.objects.create(type="D", user=request.user)
Didn't test it, though.

Related

Django is rendering an awkward result

Basically, I am working on a django project, and whenever I insert data into the database, the result is weirdly formatted.
this is my model
customer.py
class Customer(models.Model):
user = models.OneToOneField(User,null=True,blank=True,on_delete=models.CASCADE)
name = models.CharField(max_length=200, null=True)
email= models.CharField(max_length=200, null=True)
phone_number= models.CharField(max_length=200, null=True)
def __str__(self):
return self.name
Now, say I have saved a new customer
new_customer = Customer.objects.create(name="Henry",email="henry#mail.com",phone_number="+330145786259")
new_customer.save()
when i try to retrieve the customer name i get this:
print(new_customer.name)
>('henry',)
Anyone has any insight for me???
I tried to recreate the model on a new project but still having the same result
In your customer class, you have defined a 1:1 relationship with the in-built user model class of django. And when you are creating the customer object, new_customer, you have not specified the 'user' attribute; hence, your customer object is missing a key element.
The user object already has an in-built field for storing names. It is 'first_name' and 'last_name.' You need to create a user model first before being able to create your 'Customer' model.
Your models.py should look something like this:
from django.contrib.auth.models import User
class Customer(models.Model):
user = models.OneToOneField(User, null=True, blank=True, on_delete=models.CASCADE)
phone_number= models.CharField(max_length=200, null=True)
def __str__(self):
return self.user.first_name
# to return email -> self.user.email
Now to create a 'Customer' object in view.py:
from django.contrib.auth.models import User
from .models import Customer
# create a user object
myuser = User.objects.create_user(username='john', email='jlennon#beatles.com', password='glass onion')
# pass the user object to the customer model
mycustomer = Customer.objects.create(user=myuser, phone_number=123456789)
# save the customer object
mycustomer.save()
Explore django ModelForms to define the user model as per your specifications, e.g, if you don't require your users to have passwords associated with them, etc.
After much testing, I realized why I was getting the weird output.
I was directly passing data from a form to the object creation method, like so:
data = json.loads(request.body)
new_customer = Customer.objects.create(name=data['name'],email="henry#mail.com",phone_number="+330145786259")
new_customer.save()
So assigning the received data to a variable before passing it to the object creation method seems to be the right way of doing things... At least, it is working for me.

How to execute CASCADE on delete?

I have this model in Django, where a person has the same information from the user provided by Django plus a little bit more information. When I create a new person it requires to create a new user also, that's fine. But when I delete a person the user still remains on my database. What am I missing here ? I would like to delete the user too.
class Person(models.Model):
user = OneToOneField(User)
gender = CharField(max_length=1, choices=GenderChoices, blank=True, null=True)
birth_date = DateField(blank=True, null=True)
def __unicode__(self):
return self.user.username
Try to override the delete method on the model (code not tested):
class Person(models.Model):
user = OneToOneField(User)
gender = CharField(max_length=1, choices=GenderChoices, blank=True, null=True)
birth_date = DateField(blank=True, null=True)
def __unicode__(self):
return self.user.username
def delete():
theuser = User.objects.get(id=user)
theuser.delete()
I have found some relevant documentation about CASCADE usage in Django here.

Django filter ManyToMany relationship from a single object

I've got an object in Django, and one of it's properties is a ManyToMany relationship.
Ok, now I've got the object, and want to know if there's data related, how can I do it?
Here's the code:
u = request.user
ide = request.POST['id']
defob = DefObjc.objects.get(id=ide)
if defob.filter(student_def=u).exists():
#do things
And here's the object class:
class DefObjc(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
date = models.DateTimeField(blank=True, null=True)
student_def = models.ManyToManyField(User, related_name='DefObjc_relation', blank=True, null=True)
How can I do that?
The result of get() is a model instance, not a QuerySet, so you can't filter on it. Instead:
if defob.student_def.filter(id=u.id).exists():
# do things

How to use RelatedManager add() method in save()

Banging head against the wall again.
I'm trying to add tags using other known fields
# models.py
class MyModel(models.Model):
...
tags = models.ManyToManyField(Tag, blank=True)
field_m2m = models.ManyToManyField('M2mModel', blank=True)
field_fk = models.ForeignKey('FkModel', blank=True, null=True)
...
def save(self, *args, **kwargs):
for inst in self.field_m2m.all():
self.tags.add(Tag.objects.get(name=inst.name))
self.tags.add(Tag.objects.get(name=self.field_fk.name))
super(MyModel, self).save(*args, **kwargs)
class FkModel(models.Model):
name = models.CharField(max_length=255, unique=True)
...
class M2mModel(models.Model):
name = models.CharField(max_length=255, unique=True)
...
I am 100% sure my field_m2m and field_fk aren't empty and what's not less important: there are instances corresponding to EXISTING tags. I have other functions covering this part well. I have also tried hardcoding the strings (Tag.objects.get(name="mystring")) to be 101% sure.
Yet, no tags are assigned through admin panel.
I tried to go through the steps in shell and it works there.
>>> m = MyModel.objects.get(name='something')
>>> t = Tag.objects.get(name='sometag')
>>> m.tags.add(t)
>>> m.tags.all()
[<Tag: sometag>]
How to make it work from save() method?
Also until the the model instance is created for the first time, traceback is complaining about: "<MyModel: Something>" needs to have a value for field "mymodel" before this many-to-many relationship can be used.
I guess I should save the model instance before even doing aforementioned assignments, right? How can I do it all at once?
Seems to me that your MyModel instance must be saved into database before saving any relationships. It makes sense because for the relationships, the MyModel's id is needed. So you can change the order of the save method like this:
def save(self, *args, **kwargs):
# notice that super class save go first.
super(MyModel, self).save(*args, **kwargs)
# also this cicle doesn't make sense since self is a newly
# created instance so it won't have anythin in field_m2m.all()
for inst in self.field_m2m.all():
self.tags.add(Tag.objects.get(name=inst.name))
# this should work ok if get returns any tag.
self.tags.add(Tag.objects.get(name=self.field_fk.name))
Hope this helps!
Figured it out thanks to this hint: https://stackoverflow.com/a/6200451/1344854
Model save() method stays default. First I tied a tag to my FkModel and M2mModel instances. The rest of the job is done in ModelAdmin.
# models.py
class FkModel(models.Model):
name = models.CharField(max_length=255, unique=True)
tag = models.ForeignKey(Tag, blank=True, null=True, related_name='fk')
...
class M2mModel(models.Model):
name = models.CharField(max_length=255, unique=True)
tag = models.ForeignKey(Tag, blank=True, null=True, related_name='m2m')
...
# admin.py
class MyModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
form.save() # as I learned, without saving at this point, obj.field_m2m.all() will be empty
tags = []
if obj.field_m2m.all():
tags += [m2m.tag for m2m in obj.field_m2m.all()]
if obj.field_fk:
tags += [obj.field_fk.tag]
form.cleaned_data['tags'] = tags
super(MyModelAdmin, self).save_model(request, obj, form, change)

Models bleeding together in models.py?

In Django I'm trying to write a ModelForm for a ContactForm and when I try to load the page containing the form it says that it doesn't exist. Then when I try to render the other form I had previously written it says that
Caught AttributeError while rendering: 'CashtextsForm' object has no attribute 'subject'
'Subject' is a field in the form that I was trying to render in ContactForm. So is there some certain order I have to list them in models.py? Here's that code:
# Create your models here.
from django.db import models
from django.forms import ModelForm
class Cashtexts(models.Model):
cashTexts = models.CharField(max_length=100, blank=True) #change me to a website filter
superPoints = models.CharField(max_length=100, blank=True)#chance to "superPoints _Username"
varolo = models.CharField(max_length=100, blank=True)
swagbucks = models.CharField(max_length=100, blank=True)
neobux = models.CharField(max_length=100, blank=True)
topline = models.CharField(max_length=100, blank=True)
Paidviewpoint = models.CharField(max_length=100, blank=True)
cashcrate = models.CharField(max_length=100, blank=True)
def __unicode__(self):
return self.cashcode
class Contact(models.Model):
sender = models.EmailField()
subject = models.CharField(max_length=25)
message = models.TextField()
class CashtextsForm(ModelForm):
class Meta:
model = Cashtexts
def __unicode__(self):
return self.subject
class ContactForm(ModelForm):
class Meta:
model = Contact
I previously had them arranged as Model-Modelform, Model-Modelform but hereit shows them as the way I now currently have them.
Also Is there any advantages to write just forms? Right now I'm more comfortable writing model forms over forms(I dont imagine they are much differnt) but if I only wrote model forms would I be missing out on features? So is there anything I missed on how t write multiple forms in models.py or did I have them written worng? or can i not create them via the command syncdb?
The __unicode__(self) method should be part of your Contact class
class Contact(models.Model):
sender = models.EmailField()
subject = models.CharField(max_length=25)
message = models.TextField()
def __unicode__(self):
return self.subject
It doens't make sense inside CashtextsForm as that does not "know" a subject attribute.
Yes, your form really does not have subject, just remove __unicode__ definition and everything will be ok.
This is because of declarative style of django code. If you want to inspect your objects use pdb module and dir builtin.
You will use ModelForm subclasses almost every time, but sometimes you will need a form which can not be built from model. In this case django will help you to describe such form and to use form clean and field validation.
the subject field is defined in the model and not in the modelform, since a modelform can be initialized without a model instance it is not safe to do something like this:
def __unicode__(self):
return self.instance.subject
What you can do (but I do not really see the point of doing this):
def __unicode__(self):
if getattr(self, 'instance') is not None:
return self.instance.subject
return super(CashtextsForm, self).__unicode__()

Categories