Work with instance in session before save in database - python

I have a system with several steps. Each step increments one single object instance.
I want to save the instance in db only in the final step, on others just update the instance I saved in the session.
My model class seems like this:
class Proposta(models.Model):
Modelo = models.ForeignKey("ModeloVersao", verbose_name="Modelo")
Pacotes = models.ManyToManyField("PacoteModelo", null=True, blank=True)
Opcionais = models.ManyToManyField("ItemModelo", null=True, blank=True)
RevestimentoInterno = models.ForeignKey("RevestimentoInternoModelo", verbose_name="Revestimento Interno")
Cor = models.ForeignKey("CorModelo")
CorSecundaria = models.ForeignKey("CorModeloSecundaria", verbose_name="Cor secundária", null=True, blank=True)
Data = models.DateTimeField(auto_now_add = True)
Status = models.CharField("Status", choices=STATUS_PROPOSTA, max_length=10)
Cliente = models.ForeignKey("Cliente")
Here's my problem:
When I try to add or retrieve m2m fields it obviously throws a ValueError with the message 'Proposta' instance needs to have a primary key value before a many-to-many relationship can be used.
I successfully got the wanted result by creating my obj instance with pk=0 but I'm sure it isn't the best way, if there is.
Do exist a way of doing that without cheating like this.
Any help would be great.
Thanks

You might find the answers to this question helpful.
Summary for quick reference:
Use ModelForms - Based on the ModelForms documentation
Save it to DB, but use a status field - I think this is less than ideal
I might add that the documentation specifically explains how to deal with M2M fields, in the section that explains The save() method.
Of those, I recommend using ModelForms. Hopefully this helps!

Related

Creating models/database Django FK

I’m a student bachelor ICT and currently making a simple webapp. I have no experience with python and django, so im struggling a bit.
I have a running empty MARIADB in a Linux Kali VM.
I have made a ERD from my web app in visual paradigm. I exported the .dll and created the database in MySQL workbench. I used inspectdb to import the django/python code for my models.py.
So far so good.
Django offers the User module, so i didn’t make an own user class
(auth_user).
The problem is: How do I build the relation with the auth_user class between a class i created called “case”? It’s asking for a default FK value and i have no idea what i means... I googled everywhere, but just “don’t understand”.
“Class case“ i want to set a relation with a user class. Explanation: A user can create one or more Cases.
“Class item”. A case can have or more Items (under investigation).
The error I am receiving when declaring a FK in Class item with Case:
“You are trying to add a non-nullable field 'zaakid' to gegevensdrager without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py“
Default value!? I just want a row in Class Item referring to the Case primairy key ID. I have no clue what the default should be...
Example of the code:
class Case(models.Model):
registratienummer = models.CharField(db_column='Registratienummer', unique=True, max_length=10, validators=[RegexValidator(r'^[0-9]{10}$')])
onderzoeksname = models.CharField(db_column='Onderzoeksnaam', blank=False, max_length=255)
Made_by = models.ForeignKey('AuthUser', db_column='Aangemaakt Door', null=True, on_delete=models.SET_NULL)
class Item(models.Model):
merk = models.CharField(db_column='Merk', max_length=255)
type = models.CharField(db_column='Type', max_length=255, blank=True, null=True)
serienummer = models.CharField(db_column='Serienummer', max_length=255, blank=False)
imei = models.CharField(db_column='IMEI', max_length=15, blank=True, null=True, validators=[RegexValidator(r'^[0-9]{15}$')])
#gegevensdrager_soortsocode = models.ForeignKey('GegevensdragerSoort', models.DO_NOTHING, db_column='Gegevensdrager_SoortSoCode')
#zaakid = models.ForeignKey('Zaak', db_column='Zaak_referentie', on_delete=models.DO_NOTHING)
After that I tried the inspectdb > models.py. This works but I want to add constraints etc, but it doesn’t work for some reason. It’s giving me all kinds of traceback I can’t explain. But first things first, my post is long enough. Hope someone can help me out a bit, since I’m quite stressed out at the moment.
With kind regards,
Florus.
If the database is already populated with data and you add a new field to that data, then Django needs something to assign to the data already in the database. In this case, you had data that didn't already have a foreign key associated with them, so the requested default is what the program should automatically associate with the previous data.
If you remove the data from your database, this should run normally, or you can give it data to populate those Cases with and manually change it later.

Problems saving Django ORM object with a many to one relationship

I have this model:
class Answer(models.Model):
order = models.IntegerField()
question_key = models.IntegerField()
answer_index = models.IntegerField()
user_session = models.ForeignKey(
UserSession, on_delete=models.CASCADE, related_name="answers"
)
user_session cannot be None and I'd love to keep it that way.
This is how I'm trying to save an Answer object:
answer = Answer(
question_key=question_key,
answer_index=answer_index,
user_session=user_session,
order=answer_order,
)
answer.save()
But I get the error: ValueError: save() prohibited to prevent data loss due to unsaved related object 'user_session'.
My research suggests that I need to save the Answer object prior to adding the user_session to it. However, I can't do that if I would like to preserve the not null constraint on the Answer model.
Is there a better way to solve this or should I just allow Answer.user_session to be nullable?
That error is not about unsaved Answer, but about unsaved user session. You need to save all related objects (in that case that is user_session) before referencing it in other objects as foreign key, because object can not be referenced when it does not have an ID (and it does not have it until saved to database)
You didn't save the UserSession object (user_session), hence the error; it's a Many-to-One relationship so you can directly use the UserSession object as the value of user_session field while creating an instance of Answer.
So first save the UserSession object and then refer it directly in Answer model instantiation.
For example:
user_session.save()
answer = Answer(
question_key=question_key,
answer_index=answer_index,
user_session=user_session,
order=answer_order,
)
answer.save()
Also you're not doing anything after instantiating Answer, so you can use create directly:
answer = Answer.objects.create(
question_key=question_key,
answer_index=answer_index,
user_session=user_session,
order=answer_order,
)

How can I store history of ManyToManyField using django-simple-history.

How can I store history of ManyToManyField using django-simple-history. I used HistoricalRecords with attribute m2m_filds but it is throwing error: unexpected keyword argument 'm2m_fields'
I'm macro1 on GitHub, and I guess de facto maintainer of django-simple-history.
From your question it seems that you're just asking about general ManyToManyField support compared with other fields. The short answer is that we do not currently support it.
ManyToManyFields actually create an in-between model that represents the relationship between the two models you're working with.
If you want tracking on that relationship I would suggest making a 'through' model representing the relationship and passing that into the ManyToManyField constructor. You could then register that through model to have its history tracked. If you get errors like "unexpected keyword argument 'm2m_fields'" with that set up please open an issue in our tracker.
Even though django-simple-history does not allow to have history tables for many to many relations there is actually a way to achieve this.
What you can do is that you manually create the many to many table and instead of using djangos add and remove you simply create and delete the relations. If you look at it with an example we would have:
class Class(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
history = HistoricalRecords()
class Student(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
classes = models.ManyToManyField(Class)
history = HistoricalRecords()
you can manually create the many to many table with:
class Class(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
history = HistoricalRecords()
class Student(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
history = HistoricalRecords()
class StudentClasses(models.Model):
student = models.ForeignKey(Student)
class = models.ForeignKey(Class)
history = HistoricalRecords()
if you now use:
StudentClasses.objects.create(student=student, class=class) instead of student.classes.add(class) and delete() instead of student.classes.remove(class) you will have everything tracked in a history table and the same many to many table.
As the author of django-simple-history says this isn't possible to detect change in only specific fields because
As you already know simple-history doesn't look at the values being
saved at all. It blindly saves a new historical version on every save
or delete signal.
He also says it may be possible Field Tracker do this job.

Bypassing custom Model Manager to get ManyToMany objects

I'm working on a maintenance project which has a model say Business with a custom Model Manager. This custom Model Manager adds some extra filter to all the queries executing on Business models. This Business model has a ManyToMany field to self named Trainers. So far so good, the issue comes in when I try to fetch all the Trainers associated with the Business without applying those filters.
The Business model is as given below:
class Business(Basetable):
#status P=publish H=inactive D=draft N=new
name = models.CharField(max_length=120)
slug = models.SlugField(max_length=150)
logo=models.OneToOneField("BusinessLogo",null=True,on_delete=models.SET_NULL)
categories = models.ManyToManyField("BusinessCategory",related_name='allcategories',null=True)
price_type = models.CharField(max_length=2,
choices=PRICE_CHOICES,
default=YEARLY, null=True, blank=True)
achievements = models.TextField(null=True, blank=True)
years_of_experience = models.FloatField(null=True, blank=True)
trainers = models.ManyToManyField("self",related_name='btrainers',null=True, blank=True, symmetrical=False)
expense=models.IntegerField(null=True,blank=True)
objects= CityManager()
def get_trainers(self):
return self.trainers.all()
get_trainers is the function which returns all the Trainers associated with the Business, however I want the results to bypass the CityManager and use the default Manager.
Any pointers will be appreciated.
Update:
using use_for_related_fields = False does not work. I found a related bug here. Is there a work around? I know that overriding the default objects is not a good practice, however this is what I have received.
In general, it's better to avoid filtering results in the default Manager:
It's a good idea to be careful in your choice of default manager in order to avoid a situation where overriding get_queryset() results in an inability to retrieve objects you'd like to work with.
But if you can't change the default Manager for backwards-compatibility reasons, you can still explicitly create a plain Manager and get your results using that.
class Business(Basetable):
...
objects = CityManager() # Still the first listed, and default
plain_objects = models.Manager()
Now that you have a plain Manager, use it to explicitly access the desired objects:
def get_trainers(self):
return Business.plain_objects.filter(btrainers__id=self.id)

Django get_or_create does not return a usable Model object in clean method of ModelForm

Hello,
I have bound a ModelForm to one of my model that contains a ForeignKey to another model everything driven by a CreateView. What I want to achieve is to create the model object corresponding to the foreign key if it doesn't exist before the form is overall validated and the final object created in database.
Below the models I use:
class UmsAlerting(models.Model):
alert_id = models.IntegerField(primary_key=True, editable=False)
appli = models.ForeignKey('UmsApplication')
env = models.ForeignKey('UmsEnvironment')
contact = models.ForeignKey('UmsContacts')
custom_rule = models.ForeignKey('UmsCustomRules', null=True, blank=True)
class UmsApplication(models.Model):
appli_id = models.IntegerField(primary_key=True)
trigram_ums = models.CharField(max_length=4L)
class UmsContacts(models.Model):
contact_id = models.IntegerField(primary_key=True)
mail_addr = models.CharField(max_length=100L)
class UmsEnvironment(models.Model):
env_id = models.IntegerField(primary_key=True)
env_name = models.CharField(max_length=5L)
The model bound to the form is UmsAlerting. The model object I want to create if it doesn't exist is UmsContacts. I managed to use the field's clean method in my ModelForm of the contact field and use the get_or_create method like below:
def clean_contact(self):
data = self.cleaned_data['contact']
c, _ = UmsContacts.objects.get_or_create(mail_addr=data)
return c
It perfectly works when the contact is already in the database but when it needs to be created my form return a ValidationError on the contact field saying "This field cannot be null". If I submit the same form a second time without changing anything the UmsAlerting object is well created with no validation error.
My guess is that, for a reason I don't get, when get_or_create is used to create a UmsContacts object it cannot be used to create the new UmsAlerting object. So in clean_contact method the get is working and returns the UmsContacts object but the create part doesn't. It'd be like the UmsContacts object is saved when the whole form is validated but not before as I'd want it to.
Anyone could help me find out what is the problem ? Is using the clean method not the best idea ? Is there another strategy to use to take around this problem ?
Thanks in advance for your help.
It's probably because the object you are creating expects value for contact_id. If you use contact_id field for just setting object id -then you do not have to create it at all. Django takes care of Id's automatically.
Also. field clean method should return cleaned data not object. That creates whole lot more problems on its own.

Categories