Quantcast

How to limit a ManyToManyField to three choices?

classic Classic list List threaded Threaded
15 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

How to limit a ManyToManyField to three choices?

greenie2600
Hi all -

I have two models with a many-to-many relationship: Restaurant and
Cuisine. The Cuisine table contains, e.g., "Italian", "Mexican",
"Chinese", etc. Each Restaurant record can be associated with one or
more Cuisines.

Here's the thing: I'd like to limit this to three Cuisines per
Restaurant. So when editing the record for "Bob's Pan-Asian Buffet",
the user would be able to check "Japanese", "Chinese", and "Korean",
but wouldn't be able to check a fourth box.

My question: can this be enforced within the model, or is this
something I'd have to build into my interface layer?

Here's my models.py.

from django.db import models
from django.contrib.auth.models import User

class Restaurant( models.Model ):

    user = models.ForeignKey( User )
    name = models.CharField( max_length = 128 )
    slug = models.CharField( max_length = 24, unique = True )
    cuisines = models.ManyToManyField( 'Cuisine' )

    def __unicode__(self):
        return self.name

class Cuisine( models.Model ):

    name = models.CharField( max_length = 32 )

    def __unicode__(self):
        return self.name

    class Meta:
        ordering = ['name']

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

gontran
Hi greenie,

you just need to override the save method from your model Restaurant.
Before saving each instance, you check if the restaurant already  has
3 Cuisine's objects associated. If yes, you don't save the instance
and raise an error for exemple, if no, just call the standard method
of the super class Model.
You will find more infos in the django documentation:
http://docs.djangoproject.com/en/dev/topics/db/models/#overriding-predefined-model-methods

On 11 mar, 18:06, greenie2600 <[hidden email]> wrote:

> Hi all -
>
> I have two models with a many-to-many relationship: Restaurant and
> Cuisine. The Cuisine table contains, e.g., "Italian", "Mexican",
> "Chinese", etc. Each Restaurant record can be associated with one or
> more Cuisines.
>
> Here's the thing: I'd like to limit this to three Cuisines per
> Restaurant. So when editing the record for "Bob's Pan-Asian Buffet",
> the user would be able to check "Japanese", "Chinese", and "Korean",
> but wouldn't be able to check a fourth box.
>
> My question: can this be enforced within the model, or is this
> something I'd have to build into my interface layer?
>
> Here's my models.py.
>
> from django.db import models
> from django.contrib.auth.models import User
>
> class Restaurant( models.Model ):
>
>     user = models.ForeignKey( User )
>     name = models.CharField( max_length = 128 )
>     slug = models.CharField( max_length = 24, unique = True )
>     cuisines = models.ManyToManyField( 'Cuisine' )
>
>     def __unicode__(self):
>         return self.name
>
> class Cuisine( models.Model ):
>
>     name = models.CharField( max_length = 32 )
>
>     def __unicode__(self):
>         return self.name
>
>     class Meta:
>         ordering = ['name']

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

gontran
edit: you don't need to raise an error if the restaurant already has 3
Cuisine's objects associated, you just need to return a string with
the explanation of the error.

On 11 mar, 20:11, gontran <[hidden email]> wrote:

> Hi greenie,
>
> you just need to override the save method from your model Restaurant.
> Before saving each instance, you check if the restaurant already  has
> 3 Cuisine's objects associated. If yes, you don't save the instance
> and raise an error for exemple, if no, just call the standard method
> of the super class Model.
> You will find more infos in the django documentation:http://docs.djangoproject.com/en/dev/topics/db/models/#overriding-pre...
>
> On 11 mar, 18:06, greenie2600 <[hidden email]> wrote:
>
>
>
>
>
>
>
> > Hi all -
>
> > I have two models with a many-to-many relationship: Restaurant and
> > Cuisine. The Cuisine table contains, e.g., "Italian", "Mexican",
> > "Chinese", etc. Each Restaurant record can be associated with one or
> > more Cuisines.
>
> > Here's the thing: I'd like to limit this to three Cuisines per
> > Restaurant. So when editing the record for "Bob's Pan-Asian Buffet",
> > the user would be able to check "Japanese", "Chinese", and "Korean",
> > but wouldn't be able to check a fourth box.
>
> > My question: can this be enforced within the model, or is this
> > something I'd have to build into my interface layer?
>
> > Here's my models.py.
>
> > from django.db import models
> > from django.contrib.auth.models import User
>
> > class Restaurant( models.Model ):
>
> >     user = models.ForeignKey( User )
> >     name = models.CharField( max_length = 128 )
> >     slug = models.CharField( max_length = 24, unique = True )
> >     cuisines = models.ManyToManyField( 'Cuisine' )
>
> >     def __unicode__(self):
> >         return self.name
>
> > class Cuisine( models.Model ):
>
> >     name = models.CharField( max_length = 32 )
>
> >     def __unicode__(self):
> >         return self.name
>
> >     class Meta:
> >         ordering = ['name']

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

Bugzilla from neostead@go2.pl
In reply to this post by greenie2600
Dnia 11-03-2011 o 18:06:43 greenie2600 <[hidden email]> napisał(a):

> Hi all -
>
> I have two models with a many-to-many relationship: Restaurant and
> Cuisine. The Cuisine table contains, e.g., "Italian", "Mexican",
> "Chinese", etc. Each Restaurant record can be associated with one or
> more Cuisines.
>
> Here's the thing: I'd like to limit this to three Cuisines per
> Restaurant. So when editing the record for "Bob's Pan-Asian Buffet",
> the user would be able to check "Japanese", "Chinese", and "Korean",
> but wouldn't be able to check a fourth box.
>
> My question: can this be enforced within the model, or is this
> something I'd have to build into my interface layer?
>


You can limit choices on model level:

http://docs.djangoproject.com/en/1.2/ref/models/fields/#django.db.models.ForeignKey.limit_choices_to

If query is too complicated (cuz u want to access object's data, witch  
isn't yet available), you still can limit choices on form level, by  
overriding __init__


class RestaurantForm(forms.ModelForm):
     cuisines = forms.ModelMultipleChoiceField(Sklep)

     class Meta:
         model = Restaurant

     def __init__(self, *args, **kwargs):
         super(RestaurantForm, self).__init__(*args, **kwargs)
         self.fields[cuisines].queryset =  
Cuisine.objects.filter(pk__in=[fancy query])




--
Linux user

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

greenie2600
In reply to this post by gontran
gontran -

Thanks.

However, I tried the sample code in your link, and I don't think it
will work. Returning a string from the overridden save() method
prevents the record from being saved to the database, but by the time
the save() method is invoked, my ModelForm (and consequently, I
presume, the underlying Model) has already been tested as valid. The
Restaurant isn't saved, but the form isn't redisplayed and no error
message is shown, and code execution proceeds as if the form were
valid (because it *is* valid; it just wasn't saved).

I think I need to override the model validation instead. Perhaps I
need to override Model.clean_fields()?

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

greenie2600
In reply to this post by Bugzilla from neostead@go2.pl
bagheera -

I had seen the limit_choices_to parameter, but I thought it controlled
*which* choices are available to the user - not *how many* they're
allowed to choose.

I want to show the user a list of 20 or 30 cuisines, but forbid them
from checking more than three.

Can you show me an example of how I'd use limit_choices_to to limit
the *number* of choices the user can select?



On Mar 11, 2:35 pm, bagheera <[hidden email]> wrote:

> Dnia 11-03-2011 o 18:06:43 greenie2600 <[hidden email]> napisał(a):
>
> > Hi all -
>
> > I have two models with a many-to-many relationship: Restaurant and
> > Cuisine. The Cuisine table contains, e.g., "Italian", "Mexican",
> > "Chinese", etc. Each Restaurant record can be associated with one or
> > more Cuisines.
>
> > Here's the thing: I'd like to limit this to three Cuisines per
> > Restaurant. So when editing the record for "Bob's Pan-Asian Buffet",
> > the user would be able to check "Japanese", "Chinese", and "Korean",
> > but wouldn't be able to check a fourth box.
>
> > My question: can this be enforced within the model, or is this
> > something I'd have to build into my interface layer?
>
> You can limit choices on model level:
>
> http://docs.djangoproject.com/en/1.2/ref/models/fields/#django.db.mod...
>
> If query is too complicated (cuz u want to access object's data, witch  
> isn't yet available), you still can limit choices on form level, by  
> overriding __init__
>
> class RestaurantForm(forms.ModelForm):
>      cuisines = forms.ModelMultipleChoiceField(Sklep)
>
>      class Meta:
>          model = Restaurant
>
>      def __init__(self, *args, **kwargs):
>          super(RestaurantForm, self).__init__(*args, **kwargs)
>          self.fields[cuisines].queryset =  
> Cuisine.objects.filter(pk__in=[fancy query])
>
> --
> Linux user

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

Bugzilla from neostead@go2.pl
In reply to this post by greenie2600
Dnia 11-03-2011 o 21:23:38 greenie2600 <[hidden email]> napisał(a):

> gontran -
>
> Thanks.
>
> However, I tried the sample code in your link, and I don't think it
> will work. Returning a string from the overridden save() method
> prevents the record from being saved to the database, but by the time
> the save() method is invoked, my ModelForm (and consequently, I
> presume, the underlying Model) has already been tested as valid. The
> Restaurant isn't saved, but the form isn't redisplayed and no error
> message is shown, and code execution proceeds as if the form were
> valid (because it *is* valid; it just wasn't saved).
>
> I think I need to override the model validation instead. Perhaps I
> need to override Model.clean_fields()?
>

Right, i just get to that point, there is a cave rat about limiting  
choices on form level.
Depending on limiting query it may make problems if you edit this object.

afik U can't validate m2m fields on model level, but u can do it on form  
level and rise forms.ValidationError if needed in clean_field().

--
Linux user

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

Bugzilla from neostead@go2.pl
In reply to this post by greenie2600
Dnia 11-03-2011 o 21:29:29 greenie2600 <[hidden email]> napisał(a):

> bagheera -
>
> I had seen the limit_choices_to parameter, but I thought it controlled
> *which* choices are available to the user - not *how many* they're
> allowed to choose.
>
> I want to show the user a list of 20 or 30 cuisines, but forbid them
> from checking more than three.
>
> Can you show me an example of how I'd use limit_choices_to to limit
> the *number* of choices the user can select?
>

Form validation.

this should work

class RestaurantForm(forms.ModelForm):
     cuisines = forms.ModelMultipleChoiceField(Sklep)

     class Meta:
         model = Restaurant
     def clean_sklepy(self):
         cuisines_clean = self.cleaned_data[cuisines]
         if len(cuisines_clean) > 3:
                 raise forms.ValidationError('You can't choose more than  
three items!')
         return cuisines_clean




--
Linux user

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

Bugzilla from neostead@go2.pl
In reply to this post by greenie2600
Dnia 11-03-2011 o 21:29:29 greenie2600 <[hidden email]> napisał(a):

> bagheera -
>
> I had seen the limit_choices_to parameter, but I thought it controlled
> *which* choices are available to the user - not *how many* they're
> allowed to choose.
>
> I want to show the user a list of 20 or 30 cuisines, but forbid them
> from checking more than three.
>
> Can you show me an example of how I'd use limit_choices_to to limit
> the *number* of choices the user can select?
>
>
>
> On Mar 11, 2:35 pm, bagheera <[hidden email]> wrote:
>> Dnia 11-03-2011 o 18:06:43 greenie2600 <[hidden email]>  
>> napisał(a):
>>
>> > Hi all -
>>
>> > I have two models with a many-to-many relationship: Restaurant and
>> > Cuisine. The Cuisine table contains, e.g., "Italian", "Mexican",
>> > "Chinese", etc. Each Restaurant record can be associated with one or
>> > more Cuisines.
>>
>> > Here's the thing: I'd like to limit this to three Cuisines per
>> > Restaurant. So when editing the record for "Bob's Pan-Asian Buffet",
>> > the user would be able to check "Japanese", "Chinese", and "Korean",
>> > but wouldn't be able to check a fourth box.
>>
>> > My question: can this be enforced within the model, or is this
>> > something I'd have to build into my interface layer?
>>

or u can add a js script to this form field that will disallow selecting  
more than three items instead validating model (or do both).

--
Linux user

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

Bugzilla from neostead@go2.pl
In reply to this post by Bugzilla from neostead@go2.pl
Dnia 11-03-2011 o 21:39:01 bagheera <[hidden email]> napisał(a):

> Dnia 11-03-2011 o 21:29:29 greenie2600 <[hidden email]>  
> napisał(a):
>
>> bagheera -
>>
>> I had seen the limit_choices_to parameter, but I thought it controlled
>> *which* choices are available to the user - not *how many* they're
>> allowed to choose.
>>
>> I want to show the user a list of 20 or 30 cuisines, but forbid them
>> from checking more than three.
>>
>> Can you show me an example of how I'd use limit_choices_to to limit
>> the *number* of choices the user can select?
>>
>
> Form validation.
>
> this should work
>
> class RestaurantForm(forms.ModelForm):
>      cuisines = forms.ModelMultipleChoiceField(Sklep)
>
>      class Meta:
>          model = Restaurant
>      def clean_sklepy(self):
>          cuisines_clean = self.cleaned_data[cuisines]
>          if len(cuisines_clean) > 3:
>                  raise forms.ValidationError('You can't choose more than  
> three items!')
>          return cuisines_clean
>
>
>
>
Sorry, i left some of my code :P But u got the idea.

--
Linux user

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

werefr0g
In reply to this post by Bugzilla from neostead@go2.pl
Hello,

Can Model.clean() method help you? [1] You'll still have to pay attention to validation before trying to save your instances.[2]

Regards,

[1] http://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.clean
[2] http://docs.djangoproject.com/en/dev/releases/1.2/#model-validation

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

gontran
I didn't try  it, but werefr0g may be right. It seems that
Model.clean() is the method do you need. And you can still add a
javascript control for more convenience without the risk that if a
user deactivate javascript, the error will be saved.

Let us know greenie if it's ok with this method.



On 11 mar, 22:03, werefr0g <[hidden email]> wrote:
> Hello,
>
> Can Model.clean() method help you? [1] You'll still have to pay
> attention to validation before trying to save your instances.[2]
>
> Regards,
>
> [1]http://docs.djangoproject.com/en/dev/ref/models/instances/#django.db....
> [2]http://docs.djangoproject.com/en/dev/releases/1.2/#model-validation

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

greenie2600
werefr0g—

Yep, that's actually the solution I'm looking into right now. However,
I'm getting an error when trying to save a new instance of the
Restaurant model:

"'Restaurant' instance needs to have a primary key value before a many-
to-many relationship can be used."

Here's the new code that's triggering this error:


from django.db import models
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError

class Restaurant( models.Model ):

    user = models.ForeignKey( User )
    name = models.CharField( max_length = 128 )
    slug = models.CharField( max_length = 24, unique = True )
    cuisines = models.ManyToManyField( 'Cuisine', help_text = 'Choose
up to three' )

    def __unicode__(self):
        return self.name

    def clean( self ):
        raise ValidationError( type( self.cuisines ).__name__ )

class Cuisine( models.Model ):
    name = models.CharField( max_length = 32 )
    def __unicode__(self):
        return self.name
    class Meta:
        ordering = ['name']


As you can see, my custom clean() method simply raises an error
containing the type of the cuisines property. And even this is enough
to trigger the error shown above. Doing len( self.cuisines ) gives the
same result. (Raising an error containing a literal string, without
attempting to inspect the cuisines property, works as expected.)

Any ideas? It really seems like it should be possible to perform this
check at the model level. (bagheera, I'll resort to form-level
validation if I have to—but only if I have to. Client-side checks are
for UI convenience only, which is a peripheral issue.)

I'm new to Django and Python, and at this point I'm more interested in
learning the Right Way to do this (if there is one), or *why* I can't
do it at the model level (if, in fact, I can't).




On Mar 11, 4:11 pm, gontran <[hidden email]> wrote:

> I didn't try  it, but werefr0g may be right. It seems that
> Model.clean() is the method do you need. And you can still add a
> javascript control for more convenience without the risk that if a
> user deactivate javascript, the error will be saved.
>
> Let us know greenie if it's ok with this method.
>
> On 11 mar, 22:03, werefr0g <[hidden email]> wrote:
>
>
>
>
>
>
>
> > Hello,
>
> > Can Model.clean() method help you? [1] You'll still have to pay
> > attention to validation before trying to save your instances.[2]
>
> > Regards,
>
> > [1]http://docs.djangoproject.com/en/dev/ref/models/instances/#django.db....
> > [2]http://docs.djangoproject.com/en/dev/releases/1.2/#model-validation

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

Bugzilla from neostead@go2.pl
Dnia 11-03-2011 o 22:45:34 greenie2600 <[hidden email]> napisał(a):

> 'Restaurant' instance needs to have a primary key value before a many-
> to-many relationship can be used.

That is the answer You need to understand.  afiak You can't perform m2m  
validation on model level due that very reason.
That's why i did it on form level, and i think that's the only way to do  
this.  I know such things should be performed on server-side and model  
level, but i this case, is validation on form level causing any problems?

In my project i use only admin interface, so for end-user that validation  
is completely transparent.
--
Linux user

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: How to limit a ManyToManyField to three choices?

werefr0g
Well,

MAX_CUISINES = 3
   
    def clean(self):
        # three cuisines max. allowed
        if self.pk is None:
            # That's the case that raise the error
            # you're inserting a new Restaurant
            pass
        else:
            # Here, you're editing an existing Restaurant
            # (including its relashionship with Cuisine)
            if self.cuisines.count() > MAX_CUISINES:
                raise ValidationError('I said, "Choose up to three" cusines!')

As we're aiming to prevent, at model's level, saving a new Restaurant when too many related Cuisine are provided, I'm afraid that doesn't fit the needs... unless saving restaurant's intance, run a validation then delete it if it fails the validation :) I'd like to place the validation at 'cusines' field level. I'm not confortable placing it at the "whole" model's level, despite my suggestion.

I failed for the moment to find a way and I'll resume tomorrow. Maybe the following unsuccessful tries can help you (with a mix of admin interface playing and shell, both on dev server):

  * use validator on models.ManyToManyField:

      I can't even trigger a simple print('test') TT

  * use a custom field by extending models.ManyToManyField, overriding its isValidIDList method

      Ok, it is a blind test: I was looking for a way to access values from cuisines for a non saved instance of Restaurant and maybe something is lurking there. I'm a beginner.

  * accessing self's attributes for a non saved or retrieved instance of Restaurant

      # desesperately random commands (I tried more :):
     
      u = Restaurant()
      r = Restaurant.objects.all()[0]
     
      dir(u)
      # hey, a "cuisines" attribute, great!
      dir(u.cuisines)
      # TT, same error you encountered
      dir(r.cuisines)
      # fine
     
      u.clean_fields()
      # 'cusines' is not even mentionned in the error message
      r.clean_fields()
      # fine
     
      [f.verbose_name for f in r._meta.fields]
      # no cusines
     
      I believe that no control is enforced before saving, but if you try to create an new Restaurant and not provide a cuisine, the validation failed from the admin. How does it handle that? I'll try to find out. Is there a way to access data provided to 'cuisines' for an unsaved Restaurant?

Regards,

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to [hidden email].
To unsubscribe from this group, send email to [hidden email].
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.
Loading...