Django CreateView: set user before validation
Asked Answered
A

2

6

I have a model that uses different validation for its name field depending on whether the object was created by a user or by the system.

class Symbol(models.Model):
    name = models.CharField(_('name'), unique=True, max_length=64)
    creator = models.ForeignKey('User', null=True, on_delete=models.CASCADE)
    def is_system_internal(self):
        """
        whether or not this Symbol belongs to the system rather than having been created by a user
        """
        return (self.creator is None)
    def clean(self):
        """
        ensure that the Symbol's name is valid
        """
        if self.is_system_internal():
            if not re.match("^_[a-zA-Z0-9\-_]+$", self.name):
                raise ValidationError(
                    _("for system-internal symbols, the name must consist of letters, numbers, dashes (-) and underscores (_) and must begin with an underscore."),
                    params = { 'value' : self.name },
                )
        else:
            if not re.match("^[a-zA-Z][a-zA-Z0-9\-_]*$", self.name):
                raise ValidationError(
                    _("the symbol name must consist of letters, numbers, dashes (-) and underscores (_) and must begin with a letter."),
                    params = { 'value' : self.name },
                )

I want to create a Form and a CreateView with which users can create the objects. When a user creates such an object, the user should be used as the value for the value of the 'creator' field.

Currently it looks like this:

class SymbolCreateForm(forms.ModelForm):
    name = forms.CharField(max_length=Symbol._meta.get_field('name').max_length, required=True)
    class Meta:
        model = Symbol
        fields = ('name',)

class SymbolCreateView(LoginRequiredMixin, generic.CreateView):
    form_class = SymbolCreateForm
    template_name = 'main/symbol_create.html'
    def form_valid(self, form):
        # set the creator of the instance to the currently logged in user
        form.instance.creator = self.request.user
        return super(SymbolCreateView, self).form_valid(form)

I wrote it this way because that was the answer to this related question:

Accessing request.user in class based generic view CreateView in order to set FK field in Django

Unfortunately, it doesn't work: The View only allows me to create Symbols that start with an underscore, where it should do the opposite. However, when I create a Symbol, the creator field gets set correctly anyway.

I think the problem is that the creator field only gets set AFTER the clean() method has already run.

How do I set the creator field BEFORE the clean() method is called?

Alternatively, is there a better way to do what I am trying to do? It seems to me like there should be a more effective way to automate the logic that I have two different validator for the name field, the choice of which depends on the creator field.

Angelicangelica answered 30/8, 2017 at 13:21 Comment(0)
P
12

You can set instance.creator in the get_form_kwargs method.

class SymbolCreateView(LoginRequiredMixin, generic.CreateView):
    form_class = SymbolCreateForm

    def get_form_kwargs(self):
        kwargs = super(SymbolCreateView, self).get_form_kwargs()
        if kwargs['instance'] is None:
            kwargs['instance'] = Symbol()
        kwargs['instance'].creator = self.request.user
        return kwargs

Checking if kwargs['instance'] is None means that the code works with both CreateView and UpdateView.

Phillis answered 30/8, 2017 at 13:44 Comment(2)
I am getting a TypeError: "ModelFormMetaclass object argument after ** must be a mapping, not NoneType"Angelicangelica
Remember to return kwargsPhillis
T
-2

In my case, I wanted the request user to be part of the validation process. The solution proposed here was more suitable. It suggests using form_valid as follows

def form_valid(self, form):
  form.instance.author = self.request.user
  # ... do some validation here about the user for example
  if passed_validation:
    return super().form_valid(form)

  form.add_error(None, ValidationError({"author": "invalid author because ..."}))
  return super().form_invalid(form)
Tempting answered 20/4, 2018 at 16:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.