Django ManyToMany field transformation on a ModelForm - python

I have this manytomany field in my model that I have overridden with a CharField that receives a csv list of the second models name attribute.
class PostForm(ModelForm):
tests = CharField(label="tests")
class Meta:
model = Post
fields = ('title','body')
def clean_tests(self):
# Here I clean, create or retrieve and return a list of Test objects.
Now, saving and validating is alright with this code, everything works, my problem comes when I create the PostForm with an existing instance, like PostForm(instance=current_post).
The CharField should contain a csv list but it contains nothing, obviously this happens because there is no conversion happening from Test object list to test name list, the problem is I do not know where to put that code, I see no method I could override to get this done, i've looked into initial data and default properties of fields.

I'm not sure if there's a method you could override to do this -- from a look at the BaseModelForm constructor though, it looks perfectly okay to specify both the instance and initial keyword arguments together -- the instance is converted into a dict (subject to the fields and exclude options in Meta), and that dict's update method is called with initial. Something like this should work:
# build your csv list somehow (just speculation here)
tests = ','.join(test.name for test in current_post.tests.all())
form = PostForm(instance=current_post, initial={'tests': tests})

Related

Django ._meta and adding to ManyToMany fields

I haven't had much luck finding other questions that helped with this, but apologies if I missed something and this is a duplicate.
I'm trying to add to some ManyToMany fields, without having to explicitly type out the names of the fields in the code (because the function I'm working on will be used to add to multiple fields and I'd rather not have to repeat the same code for every field). I'm having a hard time using ._meta to reference the model and field objects correctly so that .add() doesn't throw an "AttributeError: 'ManyToManyField' object has no attribute 'add'".
This is simplified because the full body of code is too long to post it all here, but in models.py, I have models defined similar to this:
class Sandwich(models.Model):
name = models.CharField(max_length=MAX_CHAR_FIELD)
veggies = models.ManyToManyField(Veggie)
meats = models.ManyToManyField(Meat)
class Veggie(models.Model):
name = models.CharField(max_length=MAX_CHAR_FIELD)
class Meat(models.Model):
name = models.CharField(max_length=MAX_CHAR_FIELD)
Once instances of these are created and saved, I can successfully use .add() like this:
blt = Sandwich(name='blt')
blt.save()
lettuce = Veggies(name='lettuce')
lettuce.save()
tomato = Veggies(name='tomato')
tomato.save()
bacon = Meat(name='bacon')
bacon.save()
blt.veggies.add(lettuce)
blt.veggies.add(tomato)
blt.meats.add(bacon)
But if I try to use ._meta to get blt's fields and add to them that way, I can't. ie something like this,
field_name='meats'
field = blt._meta.get_field(field_name)
field.add(bacon)
will throw "AttributeError: 'ManyToManyField' object has no attribute 'add'".
So, how can I use ._meta or a similar approach to get and refer to these fields in a way that will let me use .add()? (bonus round, how and why is "blt.meats" different than "blt._meta.get_field('meats')" anyway?)
Why do you want to do
field = blt._meta.get_field(field_name)
field.add(bacon)
instead of
blt.meats.add(bacon)
in the first place?
If what you want is to access the attribute meats on the blt instance of the Sandwich class because you have the string 'meats' somewhere, then it's plain python you're after:
field_string = 'meats'
meats_attribute = getattr(blt, field_string, None)
if meats_attribute is not None:
meats_attribute.add(bacon)
But if your at the point where you're doing that sort of thing you might want to revise your data modelling.
Bonus round:
Call type() on blt.meats and on blt._meta.get_field(field_name) and see what each returns.
One is a ManyToManyField, the other a RelatedManager. First is an abstraction that allows you to tell Django you have a M2M relation between 2 models, so it can create a through table for you, the other is an interface for you to query those related objects (you can call .filter(), .exclude() on it... like querysets): https://docs.djangoproject.com/en/4.1/ref/models/relations/#django.db.models.fields.related.RelatedManager

Is it possible to save an object in Django by iterating over self._meta.get_fields()?

I am trying to dynamically acquire my Django Models using apps.get_model(), and then call a method from said Model to properly save the data I am passing it.
temp_model = apps.get_model(app_label='app', model_name=model)()
temp_model.save_data(data)
So far, I am able to access the Model's that I am working with, as well as the method within each Object. What I am unsure of is how to properly save the data within the Object. I have been attempting to use self._meta.get_fields() to iterate over each field and set the field equal to the data I am passing it, but this is where I am getting caught up.
Here is how I am accessing a list of the Model's fields. This is working as expected.
def save_data(self, data):
fields = [field for field in self._meta.get_fields() if field.name not in ['id', ]]
What I am unsure of is how I can then set the fields data to the appropriate piece of data that I want to pass it, and then save the Object.

Default relation for Django OneToOne Field

I'm trying to create a couple django models with a one to one relation. However I'm trying to get it so the related one to one model is automatically created. If I have something simple like this:
class MyObject(models.Model):
data = models.OneToOneField('MyData', related_name='my_object')
class MyData(models.Model):
info = models.TextField(null=True)
If I create a MyObject and access MyObject.data it will return None. I was hoping there was a way I can have it return a MyData object (just default reference).
I'd like MyObject to automatically have a related MyData object. Is there a way for me to do this or do I need to check every time to see if there's a related MyData object?
Have you seen the official doc?
d = MyData(info='whatever')
o = MyObject(data=d)
How can it be automatic if info text field has to be filled in?
after seeing your edit:
you can probably set my data to be null
o = MyObject(data=Mydata(info=None))
of course, your Mydata should now be able to accept None as their type.

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.

Pass an entire inital object in an edit form (data pre-population) in Django

I have a very complicated form and I choose to not use ModelForm since I needed flexibility and control over the fields. Since I am not using ModelForm, I can't simply do something like instance=order, where order = Order.objects.get(pk=1).
Currently I am pre-populating every field with initial in the forms.py as oppose to the views.py like this
self.fields['work_type'] = forms.ChoiceField(choices=Order.WORK_TYPE_CHOICES, initial=order.work_type)
But I was wondering if I could pass the entire order object to a form or do I have to declare initial to every field?
Is there a way to do something like
order_form = OrderEditForm(data=request.POST, initial=order)
in views.py?
I have a very complicated form and I choose to not use ModelForm since
I needed flexibility and control over the fields
Everything that you can do using a Form, you can do in a ModelForm such as adding new fields or over-riding attributes on the fields etc.
But I was wondering if I could pass the entire order object to a form
or do I have to declare initial to every field?
You can pass the order object into the form but you will still have to populate each field individually either in the forms or in the view function.
So in your view you would do something like this:
intial = {'order_number': order.number, 'order_id': order.id}
form = OrderForm(initial=initial)
The easiest way to prepopulate data to a form is passing a dictionary as first argument to de form constructor.
order_form = OrderEditForm(order.__dict__())
where __dict__() is a method that passes "order" object attributes to a dictionary with each attribute's name as a key and their content as value.
An example of how to "invent" such a method could be something like:
order_initial = Order.objects.filter(pk=order.pk).values()[0]
and then construct the form with:
order_form = OrderEditForm(order_initial)
Look at this example (how they populate values at "post" time):
For future reference to other people:
I have since found out after reading SO's comments and answers that it's better to use ModelForm even if you end up explicitly defining every field manually (using something like self.fields['foo'] = forms.CharField()).
In any case, if you are trying to pass a dictionary of current values in a form then the best (built-in) way to convert a model to a dictionary is actually using model_to_dict:
from django.forms.models import model_to_dict
order = Order.objects.get(pk=1)
dictionary = model_to_dict(order)
form = OrderEditForm(dictionary)
I got the solution from this blog. I hope this will be helpful for someone.

Categories