ManyToManyField causing Django Model Save Problems - django

I want to save a Django model instance with a ManyToManyField. When I try to do so with the create() manager, it produces the following error:
Exception Value:'post' is an invalid keyword argument for this function
Here is my model:
class Amenity(models.Model):
post=models.ManyToManyField(Post,blank=True,null=True)
name=models.CharField(max_length=50, choices=AMENITIES)
def __unicode__(self):
return str(self.name)
Here is the relevant part of the view:
if request.POST.get('amenities'):
amens=request.POST['amenities'].split(',')
p=int(post.id)
for a in amens:
Amenity.objects.create(post=p,name=a)
return HttpResponse('success')
I'm trying to save multiple amenities at one time and I'd doing so outside of a modelform because I have design in mind and I didn't want to have to create a custom field in this case.
post.id is returning the correct value here, so that doesn't seem to be the issue.
Thanks!

There are two ways you can solve this:
1) By making another database hit: (which is the safest)
p = Post.objects.get(pk=post.id)
if p:
Amenity.objects.create(post=p, name=a)
else:
...
2) Passing the id to post_id
p = int(post.id)
Amenity.objects.create(post_id=p, name=a)
EDIT:
Ok, got it working on my pc. First of all as it is Many to Many, sounds better to use posts not post as a model field. Well anyway this is how you do it:
post = Post.objects.get(pk=id)
for a in amens:
a = Amenity(name=a)
a.post.add(post) #better if it said a.posts.add(post)
a.save()

You might be able to do it via Amenity.objects.create(posts=[p],name=a) since posts expects a list though I haven't tested it myself - all my ManyToMany use a through since they add additional metadata.

You shouldn't pass post.id to post field, cause post is m2m and django take care of it. Where you set post instance ?

Related

Problems with django rest framework default field validation

I have the following problems for a nested model like this:
def Post(models.Model)
name = models.CharField(unique=True)
content = models.TextField()
def Comment(models.Model)
post = models.ForeignKey(Post)
content = models.CharField()
I created default model serializers with all fields.
Problems:
The default model serializer does not work for nested models. I have to explicitly write create/update. This has been explained in the documentation, so nothing against it. Although I think choosing sane default can cater to 99% of use cases (and for the rest, behaviour can be customisable). I will try to take a shot at this.
When I try to use json from existing post object, serializer is_valid() fails saying "unique constraint on name fails". But I wanted it to update and not create. Should is_valid not be create/update aware based on id being passed in json.
When creating a new nested json with many comments, is_valid() fails saying that "post is empty". Of course I will not have post id in the json, as post creation is yet to happen. So is_valid becomes useless. Should is_valid not depend on if id is passed in json? Also, I can not use data/validated_data without having is_valid pass.
Setting validators = [] also does not remove field validations. I have not yet found a way to suppress field validations.
I have gone through source code and documentation and spent more than a day to set up something so simple.
I must be missing something simple, so any help is appreciated.

Django - Meta.base_manager_name - make related argument in the custom queryset and manager

I have a custom model manager and a custom queryset defined specifically for related obj which means I have defined Meta.base_manager_name in the model.
I would like to use a all() manager method which fetches related obj on a OneToOneFeild.
Now I know this does not make sense since OneToOneFeild will always return one obj there is no need for a all() method. I am working on django-oscar project and am extending its "Partner" model. It originally has a field "users" with ManyToManyField and now changed to a OneToOneFeild.
The users field is called in code several times using relation user.partners.all(). I don't want to extend/modify all these places (am I being lazy here?) since I want to keep the code as upgrade friendly as possible and so instead I wanted to have all() model manager defined which will work. Not sure if it is a good idea?
the all() method takes user arg to return queryset of the user instance
class PartnerQuerySet(models.QuerySet):
def all(self, user):
return self.filter(user=user)
class PartnerManager(models.Manager):
def get_queryset(self):
return PartnerQuerySet(self.model, using=self._db)
def all(self, user):
return self.get_queryset().all(users)
class Partner(models.Model):
objects = PartnerManager()
class Meta:
base_manager_name = 'objects'
The problem is when it is used with related obj it asks for user arg which makes sense but since I am using it with a related obj I wanted to use the related obj as arg so,
user.partner.all() - should use user as arg and fetch the results
user.partner.all(user) - and I should not have to do the below
2 related questions:
1) Does this make sense - should I be doing this?
2) how I can achieve user.partner.all() without adding user in arg
PS: I know i can work with middleware to get_current_user but this function is not reliable as per some of the responses on a different question on SO.
I don't think what you are trying to do will work. Your new situation with a OneToOneField gives you the partner instance.
>>>> user.partner
<Partner xxx>
While in the old situation with the ManyToManyField, the PartnerQuerySet would've been returned.
>>>> user.partner
<PartnerQuerySet []>
A solution would be to create a custom OneToOneField, but this would most probably violate the "simple is better than complex" rule and in the end may even be more work than changing all existing .all()'s.

In Django how to do filter, followed by get queries and get a field value?

Following is a simplified models.py of one of the apps of my Django program:
#This is the custom queryset manager
class TenantManager(models.Manager):
def for_tenant(self, tenant):
return self.get_queryset().filter(tenant=tenant)
#This is one of the models:
class accountChart(models.Model):
name=models.CharField(max_length =200)
remarks=models.TextField(blank=True)
key=models.CharField(max_length=20)
tenant=models.ForeignKey(Tenant,related_name='accountchart_account_user_te nant')
objects = TenantManager()
#This is another data model, related ith FK to 1st model shown here
class paymentMode(models.Model):
name = models.TextField('Payment Mode Name')
payment_account=models.ForeignKey(accountChart,related_name='paymentMode_accountChart')
default=models.CharField('Default Payment Mode ?', max_length=3,choices=choice, default="No")
tenant=models.ForeignKey(Tenant,related_name='paymentmode_account_user_tenant')
objects = TenantManager()
Now, I'm doing the following queryset based on user inout. However, Django is throwing up errors. Request your kind help, as this thing is killing me for more than 2 days.
#Queryset:
payment_mode=paymentMode.objects.for_tenant(request.user.tenant).get(name__exact=request.POST.get('payment_mode'))
payment_account=payment_mode.account
However, Django is throwing up error with the second line of queryset. Even if I use filter instead of get, its showing error - Queryset doesn't have filter!!
From what I understand, first django is giving me all the payment modes related to this user, then getting the payment mode from the request.POST.get object and then in the second line it's trying to get the
related foreignkey. Can anyone kindly tell where I'm going wrong?
Well, sorry to disturb you all, I just got my answer as there was a typo in Queryset.
And as it should be, once I use "get" the solution is fine. Now, I'm not sure if there's a better way to do it except for caching the sessions data!!
This is an answer I wrote for future reference of others.

Django filter the queryset of ModelChoiceField - what did i do wrong?

I know that many questions exist about this same topic, but i am confused on one point.
My intent is to show two ModelChoiceFields on the form, but not directly tie them to the Game model.
I have the following:
forms.py
class AddGame(forms.ModelForm):
won_lag = forms.ChoiceField(choices=[('1','Home') , ('2', 'Away') ])
home_team = forms.ModelChoiceField(queryset=Player.objects.all())
away_team = forms.ModelChoiceField(queryset=Player.objects.all())
class Meta:
model = Game
fields = ('match', 'match_sequence')
Views.py
def game_add(request, match_id):
game = Game()
try:
match = Match.objects.get(id=match_id)
except Match.DoesNotExist:
# we have no object! do something
pass
game.match = match
# get form
form = AddGame(request.POST or None, instance=game)
form.fields['home_team'].queryset = Player.objects.filter(team=match.home_team )
# handle post-back (new or existing; on success nav to game list)
if request.method == 'POST':
if form.is_valid():
form.save()
# redirect to list of games for the specified match
return HttpResponseRedirect(reverse('nine.views.list_games'))
...
Where i am confused is when setting the queryset filter.
First i tried:
form.home_team.queryset = Player.objects.filter(team=match.home_team )
but i got this error
AttributeError at /nine/games/new/1
'AddGame' object has no attribute 'home_team'
...
so i changed it to the following: (after reading other posts)
form.fields['home_team'].queryset = Player.objects.filter(team=match.home_team )
and now it works fine.
So my question is, what is the difference between the two lines? Why did the second one work and not the first? I am sure it is a newbie (i am one) question, but i am baffled.
Any help would be appreciated.
Django Forms are metaclasses:
>>> type(AddGame)
<class 'django.forms.forms.DeclarativeFieldsMetaclass'>
They basically create a form instance according to the information given in its definition. This means, you won't get exactly what you see when you define the AddGame form. When you instantiate it, the metaclass will return the proper instance with the fields provided:
>>> type(AddGame())
<class 'your_app.forms.AddGame'>
So, with the instance, you can access the fields by simply doing form.field. In fact, it is a bit more complicated than that. There are two types of fields you can access. With form['field'] you'll be accessing a BoundField. Which is used for output and raw_input.
By doing form.fields['fields'] you'll be then accessing to a field that python can understand. This is because if the from already got any input, there's where validation and data conversion take places (in fact, those are the fields used for this, the general process of validation is a bit more complicated).
I hope this might clear a little the issue for you but as you may see, the whole form's API is really big and complicated. Is very simple for end-users but it has a lot of programming behind the curtains :)
Reading the links provides will help clear your doubts and will improve your knowledge about this very useful topic and Django in general.
Good luck!
UPDATE: By the way, if you want to learn more about Python's Metaclasses, this is a hell of an answer about the topic.
In you views.py, you have this line:
form = AddGame(request.POST or None, instance=game)
So form is a Form object of class AddGame (Side note: you should change the name to AddGameForm to avoid confusion).
Since home_team is a field in AddGame class, it's not an attribute in form object. That's why you can't access it via form.home_team.
However, Django Form API provides fields attribute to any form object, which is a dict contains all form fields. That's why you can access form.fields['home_team'].
And finally since home_team is a ModelChoiceField, it can contain a queryset attribute, that's why you can access form.fields['home_team'].queryset

Django ModelForms: Display ManyToMany field as single-select

In a Django app, I'm having a model Bet which contains a ManyToMany relation with the User model of Django:
class Bet(models.Model):
...
participants = models.ManyToManyField(User)
User should be able to start new bets using a form. Until now, bets have exactly two participants, one of which is the user who creates the bet himself. That means in the form for the new bet you have to chose exactly one participant. The bet creator is added as participant upon saving of the form data.
I'm using a ModelForm for my NewBetForm:
class NewBetForm(forms.ModelForm):
class Meta:
model = Bet
widgets = {
'participants': forms.Select()
}
def save(self, user):
... # save user as participant
Notice the redefined widget for the participants field which makes sure you can only choose one participant.
However, this gives me a validation error:
Enter a list of values.
I'm not really sure where this comes from. If I look at the POST data in the developer tools, it seems to be exactly the same as if I use the default widget and choose only one participant. However, it seems like the to_python() method of the ManyToManyField has its problems with this data. At least there is no User object created if I enable the Select widget.
I know I could work around this problem by excluding the participants field from the form and define it myself but it would be a lot nicer if the ModelForm's capacities could still be used (after all, it's only a widget change). Maybe I could manipulate the passed data in some way if I knew how.
Can anyone tell me what the problem is exactly and if there is a good way to solve it?
Thanks in advance!
Edit
As suggested in the comments: the (relevant) code of the view.
def new_bet(request):
if request.method == 'POST':
form = NewBetForm(request.POST)
if form.is_valid():
form.save(request.user)
... # success message and redirect
else:
form = NewBetForm()
return render(request, 'bets/new.html', {'form': form})
After digging in the Django code, I can answer my own question.
The problem is that Django's ModelForm maps ManyToManyFields in the model to ModelMultipleChoiceFields of the form. This kind of form field expects the widget object to return a sequence from its value_from_datadict() method. The default widget for ModelMultipleChoiceField (which is SelectMultiple) overrides value_from_datadict() to return a list from the user supplied data. But if I use the Select widget, the default value_from_datadict() method of the superclass is used, which simply returns a string. ModelMultipleChoiceField doesn't like that at all, hence the validation error.
To solutions I could think of:
Overriding the value_from_datadict() of Select either via inheritance or some class decorator.
Handling the m2m field manually by creating a new form field and adjusting the save() method of the ModelForm to save its data in the m2m relation.
The seconds solution seems to be less verbose, so that's what I will be going with.
I don't mean to revive a resolved question but I was working a solution like this and thought I would share my code to help others.
In j0ker's answer he lists two methods to get this to work. I used method 1. In which I borrowed the 'value_from_datadict' method from the SelectMultiple widget.
forms.py
from django.utils.datastructures import MultiValueDict, MergeDict
class M2MSelect(forms.Select):
def value_from_datadict(self, data, files, name):
if isinstance(data, (MultiValueDict, MergeDict)):
return data.getlist(name)
return data.get(name, None)
class WindowsSubnetForm(forms.ModelForm):
port_group = forms.ModelMultipleChoiceField(widget=M2MSelect, required=True, queryset=PortGroup.objects.all())
class Meta:
model = Subnet
The problem is that ManyToMany is the wrong data type for this relationship.
In a sense, the bet itself is the many-to-many relationship. It makes no sense to have the participants as a manytomanyfield. What you need is two ForeignKeys, both to User: one for the creator, one for the other user ('acceptor'?)
You can modify the submitted value before (during) validation in Form.clean_field_name. You could use this method to wrap the select's single value in a list.
class NewBetForm(forms.ModelForm):
class Meta:
model = Bet
widgets = {
'participants': forms.Select()
}
def save(self, user):
... # save user as participant
def clean_participants(self):
data = self.cleaned_data['participants']
return [data]
I'm actually just guessing what the value proivded by the select looks like, so this might need a bit of tweaking, but I think it will work.
Here are the docs.
Inspired by #Ryan Currah I found this to be working out of the box:
class M2MSelect(forms.SelectMultiple):
def render(self, name, value, attrs=None, choices=()):
rendered = super(M2MSelect, self).render(name, value=value, attrs=attrs, choices=choices)
return rendered.replace(u'multiple="multiple"', u'')
The first one of the many to many is displayed and when saved only the selected value is left.
I found an easyer way to do this inspired by #Ryan Currah:
You just have to override "allow_multiple_selected" attribut from SelectMultiple class
class M2MSelect(forms.SelectMultiple):
allow_multiple_selected = False
class NewBetForm(forms.ModelForm):
class Meta:
model = Bet
participants = forms.ModelMultipleChoiceField(widget=M2MSelect, required=True, queryset=User.objects.all())