Django ' s ModelForm unique

Mam Model Django, który wygląda tak.

class Solution(models.Model):
    '''
    Represents a solution to a specific problem.
    '''
    name = models.CharField(max_length=50)
    problem = models.ForeignKey(Problem)
    description = models.TextField(blank=True)
    date = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ("name", "problem")

Używam formularza do dodawania modeli, które wyglądają tak:

class SolutionForm(forms.ModelForm):
    class Meta:
        model = Solution
        exclude = ['problem']

Mój problem polega na tym, że SolutionForm nie sprawdza ograniczenia Solution i dlatego zwraca IntegrityError podczas próby zapisania formularza. Wiem, że mógłbym użyć validate_unique, aby ręcznie To sprawdzić, ale zastanawiałem się, czy jest jakiś sposób, aby złapać to w walidacji formularza i automatycznie zwrócić błąd formularza.

Dzięki.
Author: sttwister, 2010-01-26

9 answers

Rozwiązałem ten sam problem poprzez nadpisanie metody validate_unique() Modelu:


def validate_unique(self):
    exclude = self._get_validation_exclusions()
    exclude.remove('problem') # allow checking against the missing attribute

    try:
        self.instance.validate_unique(exclude=exclude)
    except ValidationError, e:
        self._update_errors(e.message_dict)

Teraz po prostu zawsze upewniam się, że atrybut Nie podany w formularzu jest nadal dostępny, np. instance=Solution(problem=some_problem) na inicjalizatorze.

 38
Author: Jarmo Jaakkola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-21 06:40:03

Jak mówi Felix, Modelformy powinny sprawdzać ograniczenie unique_together w ich walidacji.

Jednak w Twoim przypadku faktycznie wykluczasz jeden element tego ograniczenia ze swojego formularza. Domyślam się, że to jest Twój problem - jak FORMULARZ ma sprawdzić ograniczenie, jeśli połowa z nich nie jest nawet na formularzu?

 28
Author: Daniel Roseman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-26 20:19:46

Udało mi się to naprawić bez modyfikowania widoku, dodając czystą metodę do formularza:

class SolutionForm(forms.ModelForm):
    class Meta:
        model = Solution
        exclude = ['problem']

    def clean(self):
        cleaned_data = self.cleaned_data

        try:
            Solution.objects.get(name=cleaned_data['name'], problem=self.problem)
        except Solution.DoesNotExist:
            pass
        else:
            raise ValidationError('Solution with this Name already exists for this problem')

        # Always return cleaned_data
        return cleaned_data

Jedyne co muszę teraz zrobić w widoku to dodać właściwość problem do formularza przed wykonaniem is_valid.

 25
Author: sttwister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-06 11:30:23

Rozwiązanie z @sttwister jest słuszne, ale można je uprościć.

class SolutionForm(forms.ModelForm):

    class Meta:
        model = Solution
        exclude = ['problem']

    def clean(self):
        cleaned_data = self.cleaned_data
        if Solution.objects.filter(name=cleaned_data['name'],         
                                   problem=self.problem).exists():
            raise ValidationError(
                  'Solution with this Name already exists for this problem')

        # Always return cleaned_data
        return cleaned_data

Jako bonus nie wycofujesz obiektu w przypadku jego duplikacji, a jedynie sprawdzasz, czy istnieje w bazie danych.

 9
Author: boblefrag,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-05 10:03:17

Z Pomocą odpowiedzi Jarmo, poniższe wydaje mi się działać ładnie (w Django 1.3), ale możliwe, że złamałem jakiś narożny przypadek (jest wiele biletów wokół _get_validation_exclusions):

class SolutionForm(forms.ModelForm):
    class Meta:
        model = Solution
        exclude = ['problem']

    def _get_validation_exclusions(self):
        exclude = super(SolutionForm, self)._get_validation_exclusions()
        exclude.remove('problem')
        return exclude

Nie jestem pewien, ale wydaje mi się, że to błąd Django... ale musiałbym rozejrzeć się po wcześniej zgłoszonych problemach.


Edit: za szybko się odezwałem. Może to, co napisałem powyżej, zadziała w niektórych sytuacjach, ale nie w moich; skończyło się na tym, że skorzystałem bezpośrednio z odpowiedzi Jarmo.

 1
Author: Sam Hartsfield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 16:52:46

Będziesz musiał zrobić coś takiego:

def your_view(request):
    if request.method == 'GET':
        form = SolutionForm()
    elif request.method == 'POST':
        problem = ... # logic to find the problem instance
        solution = Solution(problem=problem) # or solution.problem = problem
        form = SolutionForm(request.POST, instance=solution)
        # the form will validate because the problem has been provided on solution instance
        if form.is_valid():
            solution = form.save()
            # redirect or return other response
    # show the form
 0
Author: douglaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-27 09:54:09

Jeśli chcesz, aby Komunikat o błędzie był powiązany z polem name (i pojawiał się obok niego):

def clean(self):
    cleaned_data = super().clean()
    name_field = 'name'
    name = cleaned_data.get(name_field)

    if name:
        if Solution.objects.filter(name=name, problem=self.problem).exists():
            cleaned_data.pop(name_field)  # is also done by add_error
            self.add_error(name_field, _('There is already a solution with this name.'))

    return cleaned_data
 0
Author: Risadinha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-15 09:45:41

Moje rozwiązanie opiera się na Django 2.1

Zostaw SolutionForm w spokoju, użyj metody save () w rozwiązaniu

class Solution(models.Model):
...
   def save(self, *args, **kwargs):
      self.clean()
      return super(Solution, self).save(*args, **kwargs)


  def clean():
      # have your custom model field checks here
      # They can raise Validation Error

      # Now this is the key to enforcing unique constraint
      self.validate_unique()

Wywołanie full_clean() w save () nie działa, ponieważ ValidationError zostanie anulowane

 0
Author: mb_atx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-04-26 20:57:33

W moim przypadku musiałem wykluczyć pole company i dodać je do funkcji widoku form_valid. Skończyło się na tym, że wykonałem następujące czynności (czerpiąc inspirację z różnych odpowiedzi). W moim CreateView

    def form_valid(self, form):
        cleaned_data = form.cleaned_data
        user_company = self.request.user.profile.company
        if UnitCategory.objects.filter(code=cleaned_data['code'],
                                    company=user_company).exists():
            form.add_error('code',                           _(
                'A UnitCategory with this Code already exists for this company.'))
            return super(UnitCategoryCreateView, self).form_invalid(form)
        if UnitCategory.objects.filter(color=cleaned_data['color'],
                                    company=user_company).exists():
            form.add_error('color',                           _(
                'A UnitCategory with this Color already exists for this company.'))
            return super(UnitCategoryCreateView, self).form_invalid(form)
        form.instance.company = user_company
        return super(UnitCategoryCreateView, self).form_valid(form)

W moim UpdateView musiałem wykluczyć bieżącą instancję obiektu w sprawdzaniu, czy zapytanie istnieje za pomocą exclude(pk=self.kwargs['pk'])

    def form_valid(self, form):
        cleaned_data = form.cleaned_data
        user_company = self.request.user.profile.company
        if UnitCategory.objects.filter(code=cleaned_data['code'],
                                       company=user_company).exclude(pk=self.kwargs['pk']).exists():
            form.add_error(
                'code', _('A UnitCategory with this Code already exists for this company.'))
            return super(UnitCategoryUpdateView, self).form_invalid(form)
        if UnitCategory.objects.filter(color=cleaned_data['color'],
                                       company=user_company).exclude(pk=self.kwargs['pk']).exists():
            form.add_error('color', _(
                'A UnitCategory with this Color already exists for this company.'))
            return super(UnitCategoryUpdateView, self).form_invalid(form)
        # Return form_valid if no errors raised
        # Add logged-in user's company as form's company field
        form.instance.company = user_company
        return super(UnitCategoryUpdateView, self).form_valid(form)
Nie najczystsze rozwiązanie, na które liczyłem, ale pomyślałem, że może komuś się przydać.
 0
Author: Rami Alloush,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-06 00:00:53