Will Django model instance always be populated when calling save()? - django

I have a model where I am overriding the save() function to add SearchVector's. I need to preprocess one of the instance's fields so I am overriding the save function:
class Student(models.Model):
first_name = models.CharField(max_length=255, default="")
last_name = models.CharField(max_length=255, default="")
search_vector = SearchVectorField(null=True)
def save(self, *args, **kwargs):
sv = SearchVector(Value(self.first_name)) + SearchVector(Value(self.last_name))
if len(self.last_name) > 3:
sv += SearchVector(Value(self.last_name[0:3]))
self.search_vector = sv
super().save(*args, **kwargs)
Can I expect that all fields (first_name & last_name) of the instance are always populated inside the save() function? I noticed that the instance loading function from_db seems to suggest that model instances can be created with a partial set of fields. Does this mean that first_name could be None on the save() call?

Related

Set a field value within form __init__ function

I am trying to find out an efficient way to set a field value within form init method. My models are similar to below
class Users(models.Model):
firstname = models.CharField()
lastname = models.CharField()
class profile(models.model):
user = models.ForeignKey(Users, on_delete=models.PROTECT)
class logindetails(models.model):
user = models.ForeignKey(Users, on_delete=models.PROTECT)
profile = models.ForeignKey(profile, on_delete=models.PROTECT)
login_time = models.DateField(auto_now=True)
My form is like as below:
class LoginForm(forms.ModelForm):
class Meta:
model = logindetails
fields = [__all__]
def __init__(self, *args, **kwargs):
self._rowid = kwargs.pop('rowid', None)
super(LoginForm, self).__init__(*args, **kwargs)
instance = profile.objects.get(id=self._rowid)
self.fields['user'] = instance.user <--- Facing difficulties here
Any help will be appreciated.
Django had built-in ways of setting initial form values, the documentation is available here: https://docs.djangoproject.com/en/3.0/ref/forms/api/#dynamic-initial-values

Django: How to check a Form with a m2m relation object already exists or is “unique_together”?

I am testing forms and nesting models in django. In my Project a Person can enter departure, arrival (city names) and choose a weekly day (Mon-Fri). Maybe he drives every “Tuesday” from Amsterdam to Paris. I wanted this constellation to be unique – just for fun. So If another user enters the same route the relation should be linked to the same Car.object.
Models.py
class Person(models.Model):
name = models.CharField(max_length=255, blank=False, unique=True)
route = models.ManyToManyField('Car')
def __str__(self):
return self.name
class Car(models.Model):
name = models.CharField(max_length=255, blank=False, unique=True)
weekdays = models.ForeignKey('Week', null=True, blank=False, on_delete=models.SET_NULL)
departure = models.CharField(max_length=255, blank=False)
arrival = models.CharField(max_length=255, blank=False)
class Meta:
unique_together = ['weekdays', 'departure', 'arrival'] # --- Unique combination
def __str__(self):
return self.name
class Week(models.Model):
day = models.CharField(max_length=255, blank=False, unique=True)
def __str__(self):
return self.day
views.py
class RouteCreateView(CreateView):
model = Person
template_name ="testa/create_route.html"
form_class = RouteForm
success_url = reverse_lazy('testa:testa_home')
def form_valid(self, form):
return super().form_valid(form)
forms.py
class RouteForm(forms.ModelForm):
# --- apply ChoiceField
day = forms.ModelChoiceField(queryset=None)
car_name = forms.CharField()
departure = forms.CharField()
arrival = forms.CharField()
class Meta:
model = Person
fields = [
'name'
]
def __init__(self, *args, **kwargs):
super(RouteForm, self).__init__(*args, **kwargs)
self.fields['day'].queryset = Week.objects.all()
def save(self, commit=True):
personData = super().save(commit)
data = self.cleaned_data
carData = Car(name=data['car_name'], weekdays=data['day'], departure=data['departure'], arrival=data['arrival'])
if commit:
carData.save()
personData.route.add(carData) # --- save m2m relation
return personData
If i enter two times for example „“Tuesday” from Amsterdam to Paris “ then an Error Message appears obviously, this error message (it´s german), telling me I have a double entry / Key.
Question
So my save()Method does not work because I need some kind of logic, so that Django takes the existing car.object or creates a new - if it is not a double entry. But I do not know where to start? The easiest way would be to get some kind of response from my model meta option Car.unique_together so "if it´s an “double-key error” then take the existing object". Is there a way to fetch the response? And what kind of Values it would be, only errors, could not find any hint in the doc? Or should I try some logic with exists()
That was my kind of idea / approach of a new save() 😊
def save(self, commit=True):
personData = super().save(commit)
data = self.cleaned_data
carData = Car(name=data['car_name'], weekdays=data['day'], departure=data['departure'], arrival=data['arrival'])
if commit:
# Check if database sends unique_together response
# if yes
if Car.Meta.unique_together is True:
getAlternative = Car.object.get(Meta.unique_together) # --- get the object which already exist
personData.route.add(getAlternative) # --- save m2m relation
# if not
else:
carData.save() # --- save object
personData.route.add(carData) # --- save m2m relation
return personData
obviously i get a error message: type object 'Car' has no attribute
'Meta'
Theres get_or_create for such use case: https://docs.djangoproject.com/en/2.2/ref/models/querysets/#get-or-create
...
car, created = Car.objects.get_or_create(
weekdays=data['day'],
departure=data['departure'],
arrival=data['arrival'],
defaults = dict(name=data['car_name']),
)
personData.route.add(car)
...
Obviously given name gets ignored if another car with same weekdas, departure, arrival has been found.
I suggest to put the code for creating the car and adding the route in a transaction.atomic() https://docs.djangoproject.com/en/2.2/topics/db/transactions/#django.db.transaction.atomic

Django model id turns into a tuple

I have a super class for my models as below:
class BaseModel(models.Model):
""" BaseClass vase aksare model ha """
def __init__(self, *args, **kwargs):
super(BaseModel, self).__init__(args, kwargs)
print('******> base model __init__')
status = models.IntegerField(default=1)
is_deleted = models.BooleanField(default=False)
create_user = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_creator_related")
create_date = models.DateTimeField()
update_date = models.DateTimeField()
update_user = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_updater_related")
class Meta:
abstract = True
def validate(self):
print('^^^^^^^^^^^^^^^^^^^^^^^^^^^^base validation')
and I have a profile model as below:
class Profile(BaseModel):
def __init__(self, *args, **kwargs):
super(Profile, self).__init__(args, kwargs)
""" User profile """
user = models.OneToOneField(User, related_name='profile')
mobile = models.CharField(max_length=25, null=True)
firstname_en = models.CharField(max_length=500, null=True)
lastname_en = models.CharField(max_length=500, null=True)
gender = models.IntegerField(default=0)
birth_date = models.DateTimeField(null=True)
edu_bg = models.ForeignKey('Category', related_name="profile__edu_bg", null=True)
region = models.ForeignKey('Category', related_name="profile__region", null=True)
credit = models.DecimalField(default=0, decimal_places=6, max_digits=15)
key = models.TextField(null=True)
secret = models.TextField(null=True)
I have an error when I want to insert a new userProfile as below:
TypeError: int() argument must be a string, a bytes-like object or a number, not 'tuple'.
then print the vars(userprofileObject) and realized that 'id': ((), {}), however, I have not set it. When I removed the __init__ functions or set id to None in insertion code, problem solved.
Any idea?
I need those __init__ and also don't want to set id=None in my code
This is how django's models work. You shouldn't change their __init__ method.
This is why
You may be tempted to customize the model by overriding the __init__ method. If you do so, however, take care not to change the calling signature as any change may prevent the model instance from being saved. Rather than overriding __init__, try using one of these approaches:
# Add a classmethod on the model class:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
#classmethod
def create(cls, title):
book = cls(title=title)
# do something with the book
return book
book = Book.create("Pride and Prejudice")
Source https://docs.djangoproject.com/en/1.10/ref/models/instances/#django.db.models.Model
Also read this Writing a __init__ function to be used in django model

Django change foreign key data and save

I have two models like
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
Now for an object of Article lets say
a=Article.objects.filter(id=1)
a=a[0]
I try to change the headline and the email of the author who has written this article so I do
a.heagline = "Saving foreign key does not work"
a.reporter.email = "changed#email.com"
a.save()
This saves the Article object but does not modify the Reporter.
I explicitly have to do
a.reporter.save()
to see the changes to the reporter object. As this is a Many to One relation it should also modify the Foreign key on saving
How can I save the parent Model too by just calling the childs save method
You could override the save method or just create a custom method.
class Article(models.Model):
...
# Overriding save
def save(self, *args, **kwargs):
self.reporter.save()
super(Article, self).save(*args, **kwargs)
# Creating a custom method
def save_related(self):
self.reporter.save()
self.save()
I suggest you create a custom method because it doesn't introduce unexpected behavior in save()

Why doesn't my save method work in the admin?

In my model I overwrite the save-method for my blog model to auto-populate the slug field using the slugify method.
class BlogPost(models.Model):
title = models.CharField(max_length=100,unique=True)
slug = models.SlugField(max_length=100,unique=True)
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(Author)
body = models.TextField()
category = models.ForeignKey(BlogCategory)
def save(self, *args, **kwargs):
if not self.id:
# Newly created object, so set slug
self.slug = slugify(self.title)
super(BlogPost, self).save(*args, **kwargs)
But creating a new object in the admin interface doesn't work without either setting the slug field manually or doing something like
class BlogPostAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
Basically I currently have the same functionality defined twice. Any ideas on how to avoid this? And: why doesn't work my own save method in the admin?
You should put blank=True in the definition of the slug field.