how to conditionally save a form using django multi table inheritance - django

I have the following form:
class PlaceForm(forms.ModelForm):
class Meta:
model = Place
I have the following models:
class Place(models.Model):
customer = models.ForeignKey(Customer)
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
In my view I want to conditionally save either a Place or a Restaurant depending on the incoming url.
I have tried the following:
if form.is_valid():
place = form.save(commit=False)
place.customer = customer
place.save()
if url_name == 'restaurant':
restaurant = Restaurant(place_ptr_id=place.id)
restaurant.save()
This creates a place from the form and then tries to create a restaurant, but fails with following: (1048, "Column 'customer_id' cannot be null")
This is telling me that a new row for a new place is trying to be inserted and then the restaurant row.
I see a few different options:
Convert the Place to a restaurant and save the additional to the converted object.
Conditionally change the model type of the form to either Place or Restaurant
How can I accomplish saving the different parent and child objects conditionally?

It is related to Django model inheritance: create sub-instance of existing instance (downcast)? which suggests how to add object with existing base class object.
You may want to look at my question: Derived model filefield not available
In nutshell what you have to do is
restaurant = Restaurant(place_ptr_id=place.id)
restaurant.__dict__.update(place.__dict__)
restaurant.save()

You can add null=True and blank=True.
models:
class Place(models.Model):
customer = models.ForeignKey(Customer, null=True, blank=True)
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)

Related

Django Nested Serialization

this is my serializor.py
class AddressSerializor(serializers.ModelSerializer):
class Meta:
model = Address
fields = '__all__'
class PersonSerializer(serializers.ModelSerializer):
a = AddressSerializor(many=True)
class Meta:
model = Persion
fields = ('id' ,'name', 'age', 'a' )
this is my models page :
class Persion(models.Model):
name = models.TextField(max_length=200)
class Address(models.Model):
city = models.TextField(max_length=300)
state = models.TextField(max_length=301)
cuntry = models.TextField(max_length=222)
I am getting this error:
Got AttributeError when attempting to get a value for field `a` on serializer `PersonSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Persion` instance.
Original exception text was: 'Persion' object has no attribute 'a'.
can any one help me what am i missing, as iam new to django i may miss the basic syntax too, Iam feguring this nearly 2 days no luck
Since according to your comments you have one address per person, you can technically put the address fields in the Person model, but if you want to separate it you need to link them like
class Address(models.Model):
city = models.TextField(max_length=300)
state = models.TextField(max_length=301)
cuntry = models.TextField(max_length=222)
class Persion(models.Model):
name = models.TextField(max_length=200)
address = models.OneToOneField(Address,
on_delete=models.CASCADE,
null=True, # allow field to be null
blank=True) # allow field to be blank (for admin interface)
This way the Persion would have an address field that would be of type Address model.
and your serializers
class AddressSerializor(serializers.ModelSerializer):
class Meta:
model = Address
fields = '__all__'
class PersonSerializer(serializers.ModelSerializer):
address = AddressSerializor() #note that address is the same name in the Persion model
class Meta:
model = Persion
fields = ('id' ,'name', 'address' )
in your question you had age as a field but you don't have an age field in the Persion model, you will need to add that.
check out the fields in Django docs
https://docs.djangoproject.com/en/3.2/ref/models/fields
You need to create a foreign key relationship between a Person and an Address. You can add the foreign key declaration to your models, and then generate/run migrations using python manage.py makemigrations <name-of-app> and python manage.py migrate.
class Person(models.Model):
... # the other model fields
address = models.ForeignKey(
"Address",
on_delete=models.SET_NULL,
null=True,
blank=True,
)

Using Django model inheritence to clone a table

I have a user table and want to create a backup table named deleted_users that is a clone of the user table. When a user gets deleted I want to move the record from the user table to the deleted_users table.
class LCUser(models.Model):
email = models.EmailField(max_length=100,unique=True)
password = models.CharField(max_length=100)
first_name = models.CharField(max_length=100)
last_name=models.CharField(max_length=100)
class Meta:
db_table = "user"
class LCUserDeleted(LCUser):
deleted_at = models.DateTimeField(auto_now_add=True,null=True)
class Meta:
db_table = "deleted_users"
I tried as above but this creates the user table with all the fields and a deleted_users table with 2 fields ( lcusers_ptr_id and deleted_at ), How can I create the 2nd table with all the fields without typing everything one by one ?
to achieve what you're looking for you actually need 3 models, one abstract one and two actual models:
class BaseLCUser(models.Model):
email = models.EmailField(max_length=100,unique=True)
password = models.CharField(max_length=100)
first_name = models.CharField(max_length=100)
last_name=models.CharField(max_length=100)
class Meta:
abstract = True
class LCUser(BaseLCUser):
class Meta:
db_table = "user"
class LCUserDeleted(BaseLCUser):
deleted_at = models.DateTimeField(auto_now_add=True,null=True)
class Meta:
db_table = "deleted_users"
By using an abstract model as a common parent Django will actually create full tables for each of the models.
Edit:
Regarding the unique E-mail, you might want to avoid setting it to unique on a database level, and control it through your forms, because if you create and account with email example#example.org, delete it, create another one with the same email and delete than new account, you will be trying to have two deleted users with the same email.
another option would be to remove the email field from the abstract Model, and put it in each of the child models, being unique only in the not deleted user.

form with manytomany object creation

I'm trying to make own form adding object Announcement
models.py:
class Announcement(models.Model):
person = models.ForeignKey('Person')
source = models.CharField(max_length=30)
date = models.DateField(default=datetime.now())
valid_date = models.DateField(null=True,blank=True)
class Person(models.Model):
names = models.ManyToManyField('Name')
birth_date = models.DateField(null=True, blank=True)
class Name(models.Model):
first_name = models.CharField(max_length=50,blank=True)
middle_name = models.CharField(max_length=50, blank=True)
last_name = models.CharField(max_length=50, blank=True)
(It maybe loos weird, but in my concept each Person can have more than one Name, and also the same Name can be assigned to different Persons)
forms.py
from django import forms
from backoffice.models import Announcement
class AnnouncementForm(forms.ModelForm):
class Meta:
model = Announcement
fields = ('person','signature','organisation','source', 'date')
And everything works perfectly but I have to choose Person from selectbox. And it is expected behaviour.
But in my case i'm definetely sure, that person doesn't exists in base (all announcements are for different person for very long time - so i want to change person select box to three fields, and create new person (with new names) everytime I save the announcement.
I think I know how to save many to many, that's why i don't put the views.py, but I don't know how to set the forms.py to get fields.
I tried
class AnnouncementForm(forms.ModelForm):
class Meta:
model = Announcement
fields = ('person__names__first_name','signature','organisation','source', 'date')
but got Unknown field(s) (person__names__first_name) specified for Announcement
person__name__first_name will not really work in the forms, that only works for the django admin
you have to create a custom field for the first name and then create a logic for saving on either
def clean(self):
// logic here
or
def save(self, commit=True):
// put clean data here
announcement_form = super(AnnouncementForm, self).save(commit=False)
announcement_form.save()

Show all field in OneToOneField field type

In Django 1.8.3, I have following classes and they are showing in Admin Panel:
class Address(models.Model):
address_line1 = models.CharField(max_length=64)
address_line2 = models.CharField(max_length=64)
address_line3 = models.CharField(max_length=64)
post_code = models.CharField(max_length=5)
class Customer(models.Model):
name = models.CharField(max_length=64)
address = models.OneToOneField(Address)
Now in Customer form in Admin Panel, the Address is shown as a dropdown menu. My question is: How can I show all fields of the Address class instead of a single dropdown in Customer form as it is a OneToOneField in Customer class?
Thanks in advance
If I were you, I would change the structure so that Address could be an inline. In the models.py:
class Customer(models.Model):
name = models.CharField(max_length=64)
class Address(models.Model):
costumer = models.OneToOneField(Costumer)
address_line1 = models.CharField(max_length=64)
address_line2 = models.CharField(max_length=64)
address_line3 = models.CharField(max_length=64)
post_code = models.CharField(max_length=5)
And then, in the admin.py:
class AddressInline(admin.StackedInline):
model = Address
extra = 1
max_num = 1
class CostumerAdmin(admin.ModelAdmin):
inlines = [AddressInline]
admin.site.register(Costumer, CostumerAdmin)
for field in YourModelClass._meta.get_fields():
# iterate through main model's fields
if isinstance(field, OneToOneField):
# if the field is an OneToOneField
for field2 in YourModelClass._meta.get_field(field.name).related_model._meta.get_fields():
# iterate through the OneToOneField's fields
fieldname = field2.name
fieldvalue = field2.value_from_object(getattr(instance, field.name))
# getattr simulates instance.`field.name`
else:
fieldname = field.name
fieldvalue = field.value_from_object(instance)
where YourModelClass is the model that contains more OneToOneField objects and/or other basic models. In the example above, it is Address, and instance is the instance of the model.
Please notice you don't need the instance to get the field names, but you need it if you want to get the field value.
I use this code to convert an instance of a model into a context dictionary for dynamic settings, i'm not sure it's the best solution.

How can I show a ManyToManyField value filtered on the basis of ForeignKey while editing an django model object?

I have four models in my models.py which are:
models.py
class Course(models.Model):
course_code = models.CharField(max_length=100,unique=True)
title = models.CharField(max_length=200)
short = models.CharField(max_length=50)
elective_group = models.CharField(max_length=100)
class Unit(models.Model):
title = models.CharField(max_length=100)
short = models.CharField(max_length=50)
course = models.ForeignKey(Course)
class Pattern(models.Model):
pattern_name = models.CharField(max_length=200)
class ExamSchedule(models.Model):
exam_date = models.DateTimeField()
course = models.ForeignKey(Course)
pattern = models.ForeignKey(Pattern)
**units = models.ManyToManyField(Units)**
I have all these models register with admin site, so that I can use admin functionality for these models.
My problem is when a user creates or edits a ExamSchedule object, I want the units(field) multivalue widget should contains only those values that are associated with a course as every course can have multiple units. So if user creates an Examschedule object and after selecting a course from dropdown the unit widget should only contains those units that related to the course selected.
Thanks
I have used this django plugin to do this exact thing in the admin sections. I believe it also works in the front end as well:
https://github.com/digi604/django-smart-selects