Django: how to validate m2m relationships?

前端 未结 2 2113
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-22 04:18

Let\'s say I have a Basket model and I want to validate that no more than 5 Items can be added to it:

class Basket(mod         


        
相关标签:
2条回答
  • 2020-12-22 05:00

    You can never validate relationships in the clean method of the model. This is because at clean time, the model may not yet exist, as is the case with your Basket. Something that does not exist, can also not have relationships.

    You either need to do your validation on the form data as pointed out by @bhattravii, or call form.save(commit=False) and implement a method called save_m2m, which implements the limit.

    To enforce the limit at the model level, you need to listen to the m2m_changed signal. Note that providing feedback to the end user is a lot harder, but it does prevent overfilling the basket through different means.

    0 讨论(0)
  • 2020-12-22 05:11

    I've been discussing this on the Django Developers list and have in fact tabled a method of doing this for consideration in the Django core in one form or another. The method is not fully tested nor finalised but results for now are very encouraging and I'm employing it on a site of mine with success.

    In principle it relies on:

    1. Using PostgreSQL as your database engine (we're fairly sure it won't work on Lightdb or MySQL, but keen for anyone to test this) enter code here

    2. Overriding the post() method of your (Class based) view such that it:

      1. Opens a an atomic transaction
      2. Saves the form
      3. Saves all the formsets if any
      4. Calls Model.clean() or something else like Model.full_clean()
    3. In your Model then, in the method called in 2.4 above you will see all your many to many and one to many relations in place. You can validate them and throw a ValidationError to see the whole transaction rolled back and no impact on the database.

    This is working wonderfully for me:

    def post(self, request, *args, **kwargs):
        # The self.object atttribute MUST exist and be None in a CreateView. 
        self.object = None
        self.form = self.get_form()     
        self.success_url = reverse_lazy('view', kwargs=self.kwargs)
    
        if connection.vendor == 'postgresql':
            if self.form.is_valid():
                try:
                    with transaction.atomic():
                        self.object = self.form.save()
                        save_related_forms(self) # A separate routine that collects all the formsets in the request and saves them
    
                        if (hasattr(self.object, 'full_clean') and callable(self.object.full_clean)):
                            self.object.full_clean()
                except (IntegrityError, ValidationError) as e:
                    if hasattr(e, 'error_dict') and isinstance(e.error_dict, dict):
                        for field, errors in e.error_dict.items():
                            for error in errors:
                                self.form.add_error(field, error)
                    return self.form_invalid(self.form)                    
    
                return self.form_valid(self.form)
            else:
                return self.form_invalid(self.form)
    
        else:
            # The standard Djangop post() method
            if self.form.is_valid():
                self.object = self.form.save()
                save_related_forms(self)
                return self.form_valid(self.form)
            else:
                return self.form_invalid(self.form)
    

    And the conversation on the Developers list is here:

    https://groups.google.com/forum/#!topic/django-developers/pQ-8LmFhXFg

    if you'd like to contribute any experience you gain from experimenting with this (perhaps with other database backends).

    The one big caveat in the above approach is it delegates saving to the post() method which in the default view is done in the form_valid() method, so you need to override form_valid() as well, otherwise a post() like the one above will see you saving the form twice. Which is just a waste of time on an UpdateView but rather disastrous on a CreateView.

    0 讨论(0)
提交回复
热议问题