Django: Como eu valido exclusivo_together de dentro do modelo
-
20-09-2019 - |
Pergunta
Eu tenho o seguinte:
class AccountAdmin(models.Model):
account = models.ForeignKey(Account)
is_master = models.BooleanField()
name = models.CharField(max_length=255)
email = models.EmailField()
class Meta:
unique_together = (('Account', 'is_master'), ('Account', 'username'),)
Se eu criar uma nova conta com o mesmo nome de usuário que outro na mesma conta, em vez de me dar um erro a ser exibido no modelo, ele quebra com um integridade e a página morre. Eu gostaria que, na minha opinião, eu pudesse ir:
if new_accountadmin_form.is_valid():
new_accountadmin_form.save()
Como faço para conquistar esse problema. Existe um segundo is_valid()
tipo de método que verifica o banco de dados para violação do unique_together = (('Account', 'is_master'), ('Account', 'username'),)
papel?
Eu gostaria de não ter que pegar um integridade em minha opinião. Essa é a lógica de domínio misturada com a lógica de apresentação. Ele viola secar porque, se eu exibir o mesmo formulário em 2 páginas, terei que repetir o mesmo bloco. Ele também viola secar porque, se eu tiver duas formas para a mesma coisa, tenho que escrever o mesmo, exceto: novamente.
Solução
Existem duas opções:
a) Faça um bloco de tentativa onde salve seu modelo e capture o integridade e lide com ele. Algo como:
try:
new_accountadmin_form.save()
except IntegrityError:
new_accountadmin_form._errors["account"] = ["some message"]
new_accountadmin_form._errors["is_master"] = ["some message"]
del new_accountadmin_form.cleaned_data["account"]
del new_accountadmin_form.cleaned_data["is_master"]
b) No método limpo () do seu formulário, verifique se a linha AN existe e aumente um forms.ValidationError
com uma mensagem apropriada. Exemplo aqui.
Então, b) é ... é por isso que eu referenciado a documentação; Tudo que você precisa está lá.
Mas seria algo como:
class YouForm(forms.Form):
# Everything as before.
...
def clean(self):
""" This is the form's clean method, not a particular field's clean method """
cleaned_data = self.cleaned_data
account = cleaned_data.get("account")
is_master = cleaned_data.get("is_master")
username = cleaned_data.get("username")
if AccountAdmin.objects.filter(account=account, is_master=is_master).count() > 0:
del cleaned_data["account"]
del cleaned_data["is_master"]
raise forms.ValidationError("Account and is_master combination already exists.")
if AccountAdmin.objects.filter(account=account, username=username).count() > 0:
del cleaned_data["account"]
del cleaned_data["username"]
raise forms.ValidationError("Account and username combination already exists.")
# Always return the full collection of cleaned data.
return cleaned_data
Pelo que vale a pena - acabei de perceber que seu único_together acima está referenciando um campo chamado nome de usuário que não está representado no modelo.
O método limpo acima é chamado depois que todos os métodos limpos para os campos individuais são chamados.
Outras dicas
E de uma maneira completamente genérica. No modelo, os dois seguintes auxiliares FNS:
def getField(self,fieldName):
# return the actual field (not the db representation of the field)
try:
return self._meta.get_field_by_name(fieldName)[0]
except models.fields.FieldDoesNotExist:
return None
e
def getUniqueTogether(self):
# returns the set of fields (their names) that must be unique_together
# otherwise returns None
unique_together = self._meta.unique_together
for field_set in unique_together:
return field_set
return None
E na forma tem o seguinte FN:
def clean(self):
cleaned_data = self.cleaned_data
instance = self.instance
# work out which fields are unique_together
unique_filter = {}
unique_fields = instance.getUniqueTogether()
if unique_fields:
for unique_field in unique_fields:
field = instance.getField(unique_field)
if field.editable:
# this field shows up in the form,
# so get the value from the form
unique_filter[unique_field] = cleaned_data[unique_field]
else:
# this field is excluded from the form,
# so get the value from the model
unique_filter[unique_field] = getattr(instance,unique_field)
# try to find if any models already exist in the db;
# I find all models and then exlude those matching the current model.
existing_instances = type(instance).objects.filter(**unique_filter).exclude(pk=instance.pk)
if existing_instances:
# if we've gotten to this point,
# then there is a pre-existing model matching the unique filter
# so record the relevant errors
for unique_field in unique_fields:
self.errors[unique_field] = "This value must be unique."
Model.Meta.unique_together cria uma restrição limitada ao banco de dados, enquanto o modelform.is_valid () é baseado principalmente em tipos corretos. Evento Se ele verificasse as restrições, você teria uma condição de corrida que ainda poderia causar um integridade no SALVE ().
Você provavelmente quer pegar o IntegrityError:
if new_accountadmin_form.is_valid():
try:
newaccountadmin_form.save()
except IntegrityError, error:
# here's your error handling code