I have a model that the clean that raises a ValidationError in the model, and then I customized the form in the admin. The "myfield" is a ForeingKey.
class MyModel(models.Models):
myfield = models...
def clean(self):
if check_something_on(self.myfield):
raise ValidationError("Technical Error in myfield")
def save(self, *kwargs):
self.clean()
super(MyModel, self).save(*kwargs)
I expect that by the shell system or other methods, the myfield field is always OK.
Then, I added in the admin a form that looks like this:
class MyModelAdminForm(forms.ModelForm):
myfield = form...
def clean(self):
cleaned_data = super(MyModelAdminForm, self).clean()
myfield = cleaned_data['myfield']
if check_something_on(myfield):
forms.ValidationError("User helping Error in myfield")
return cleaned_data
What happens is that in the admin page, I end up with both messages, the technical error and the user helping error...
Is there any way to prevent this?
Thanks
Related
I'm using Django-Rest-Framework(ViewSet approach) on my project interacting with a React app. So, I'm not using Django admin nor Django forms.
My project's structure is:
View
Serializer
Model
What I need to do is to perform actions before models method calls:
Insert the request.user on a Model field.
Start a printer process after a Model.save()
.....
I have read a lot about django-way to do on Django.docs and there, the things seems to be showed for a Django-Admin like project, which is not my case.
By other hand, by reading the Stack's answers about in other topics, the way to do seems to be like: "It will work, but, It's not the right way to do that".
According to Django's documentation, the best way to perform that supposed to be by using a new file, called admin.py, where I would to register actions binding to a Model which could support save, delete, etc., but, it's not clear if this approach is to do that or only for provide a Django-Admin way to perform an action.
# app/models.py
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
user = models.ForeignKey(User)
content = models.TextField()
class Comment(models.Model):
post = models.ForeignKey(Post)
user = models.ForeignKey(User)
content = models.TextField()
# app/admin.py
from app.models import Post, Comment
from django.contrib import admin
class CommentInline(admin.TabularInline):
model = Comment
fields = ('content',)
class PostAdmin(admin.ModelAdmin):
fields= ('content',)
inlines = [CommentInline]
def save_model(self, request, obj, form, change):
obj.user = request.user
obj.save()
def save_formset(self, request, form, formset, change):
if formset.model == Comment:
instances = formset.save(commit=False)
for instance in instances:
instance.user = request.user
instance.save()
else:
formset.save()
admin.site.register(Post, PostAdmin)
According to the answers I have heard, the best way would use something like that on Models:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(MyModelForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
kwargs['commit']=False
obj = super(MyModelForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
return obj
What I want to know is:
What's the best way to to perform that actions, on which files, what's the best structure.
to insert a request.user on a Model field you can use the perform_create() method on your view class. for more information visit https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/#associating-snippets-with-users which is exactly what u want!
I'm not sure what you mean by start a printer process, but you usually can override save() method on your model class for doing a process after saving a model instace.
https://www.django-rest-framework.org/api-guide/serializers/#overriding-save-directly
The best way I found to insert the request.user on the model, as a "created_by" field, was by inserting a hidden field on the model serializer with a default data, just like these:
my_field = serializers.HiddenField(default=serializers.CurrentUserDefault())
The CurrentUserDefault() is a function wich returns the user request onto serializer.
https://www.django-rest-framework.org/api-guide/validators/#advanced-field-defaults
For actions performing after/before a save/delete, what I chose to use Django Signals,wich works as a dispatcher of actions, a little like react's redux.
https://docs.djangoproject.com/en/2.2/topics/signals/
Thank you everybody for the helpful answers.
Need help for Django model save() method.
i need a redirection back to Person list page or to Person form if some conditions false on my model save() method .
Currently i used a validation error but i am looking for redirection back to list page or form. And if i use a return statement, always get added successfully without saving.
Here i need a redirection instead of "raise ValidationError(u"Enter a valid name").
Thanks in Advance
Justin
#Models.py
from django.db import models
from django.core.exceptions import ValidationError
class Person(models.Model):
first_name = models.CharField(max_length=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
address = models.TextField(max_length=300, blank=True)
def save(self, *args, **kwargs):
if self.first_name == "": #(Some condition check, not a simple field value )
raise ValidationError(u"Enter a valid name")
# Need a redirection back to Django Admin Persons list page or to Add Person Page with error message
# But always showing added successfully, if i use a return or redirection.
else:
super(Person,self).save(*args, **kwargs)
#admin.py
from django.contrib import admin
from testapp.models import Person
from testapp.forms import PersonAdminForm
from django.contrib import messages
# Register your models here.
class PersonAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name')
#form = PersonAdminForm
#def save_model(self, request, obj, form, change):
# obj.user = request.user
# obj.save()
# messages.add_message(request, messages.INFO, 'Text of message')
admin.site.register(Person, PersonAdmin)
#forms.py
from testapp.models import Person
from django import forms
class PersonAdminForm(forms.ModelForm):
class Meta:
model = Person
Thanks friends for the reply,
I adding my actual model class here. actually i am not using any custom form or custom validation. i used only django admin interface. But i added a custom save() method in model class. And in that save() method i did some conditions on edit and add cases.
Addind data and editing data working with save() method correctly. But condition false case we have no option redirect back to admin model class listing or admin form with error message?
Here in my example can i use any other code instead of raise ValidationError("error test message")?. raise ValidationError gives django error page. if i use redirect or return give "... added successfully" message on no data saving also.
Any chance?
my code ...
class Asset(models.Model):
-----code--------
class Meta:
verbose_name_plural= "Employees Assets"
def save(self, *args, **kwargs):
----- code------
if self.pk is not None:
++++++++++ some code +++++++++++
if self.hardware.hardware_status == 0 and edit_flag == 2:
++++++++++ some code +++++++++++
elif self.hardware.hardware_status == 1 and edit_flag == 1:
++++++++++ some code +++++++++++
elif (self.hardware.hardware_status == 0 or self.hardware.hardware_status == -1) and edit_flag == 1:
++++++++++ some code +++++++++++
elif self.hardware.hardware_status == -1 and edit_flag == 2:
raise ValidationError('Cant modify Hardware, Hardware status is Lost ')
else:
raise ValidationError('Cant modify Hardware, Hardware already assigned to other staff')
self.hardware.save()
super(Asset, self).save(*args, **kwargs)
else:
if self.hardware.hardware_status == 0:
++++++++++ some code +++++++++++
else:
raise ValidationError(u'Can't assign, Hardware not available(Lost/Alreday Assigned) for assignment')
self.hardware.save()
super(Asset, self).save(*args, **kwargs)
def __unicode__(self):
return u'%s Hardware information for %s' % (self.hardware, self.employee)
That is completely the wrong place to do it. Models, deliberately, do not know anything about the request. There's no way to redirect from a save method, and you should not try to implement one. Your view is responsible for running validation and redirecting as approrpriate.
try to custom validation in admin:
forms.py:
class PersonAdminForm(forms.Form):
class Meta:
model = Person
def clean_first_name(self):
if self.first_name == "":value )
raise ValidationError(u"Enter a valid name")
else
return self.clean_first_name["first_name"]
admin.py:
class PersonAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name')]
form = PersonAdminForm
check this linkvalidation
The model doesn't do redirection. Which means, overriding your model save method is of little use here. This is a very direct case of form validation, therefore, you must use the below in forms.py
Your view is taking care of the form redirection for you, therefore, just write your code in the form.
why comes a keyerror instead of a validation error when one field is empty? The fields should be required=True by default
class form(forms.ModelForm):
adminAccount = forms.CharField()
adminPassword = forms.CharField(widget=forms.PasswordInput)
def userCheck(self, user, password):
# do something
def clean(self):
self.userCheck(self.cleaned_data['adminAccount'],
self.cleaned_data['adminPassword'])
It is your code that is raising the KeyError here:
self.userCheck(self.cleaned_data['adminAccount'],
self.cleaned_data['adminPassword'])
Because you're trying to access self.cleaned_data[field] when field was not posted.
The documentation provides an example that explains how to validate data that depends on more than one field. According to the examples you should do something like:
cleaned_data = super(form, self).clean()
adminAccount = cleaned_data.get('adminAccount')
adminPassword = cleaned_data.get('adminPassword')
if adminAccount and adminPassword:
# proceed with your validation
return cleaned_data
Also, remember that Form.clean() must return the cleaned_data dict.
Say I'm writing a multi-blog application and I want each author to use unique titles for their articles (but unique per user, not globally unique):
class Article(models.Model):
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=255)
#[...]
class Meta:
unique_together = (('title', 'owner'),)
Now, I want the author field to be auto-filled by the application:
class ArticleAdmin(ModelAdmin):
exclude = ('owner',)
def save_model(self, request, obj, form, change):
if not change:
obj.owner = request.user
obj.save()
Actually this does not work: if I try to create a new Article with an existing author-title combination, Django will not check the uniqueness (because author is excluded from the form) and I'll get an IntegrityError when it hits the database.
I thought of adding a clean method to the Article class:
def clean(self):
if Article.objects.filter(title=self.title, owner=self.owner).exists():
raise ValidationError(u"...")
But it seems that Article.clean() is called before ArticleAdmin.save_model(), so this does not work.
Several variants of this question have been asked already here, but none of the solutions seem to work for me:
I cannot use Form.clean() or other form methods that don't have the request available, since I need the request.user.
For the same reason, model-level validation is not possible.
Some answers refer to class-based views or custom views, but I'd like to remain in the context of Django's Admin.
Any ideas how I can do this without rewriting half of the admin app?
You are finding a way to bring request to customized form, in ModelAdmin, actually:
from django.core.exceptions import ValidationError
def make_add_form(request, base_form):
class ArticleForm(base_form):
def clean(self):
if Article.objects.filter(title=self.cleaned_data['title'], owner=request.user).exists():
raise ValidationError(u"...")
return self.cleaned_data
def save(self, commit=False):
self.instance.owner = request.user
return super(ArticleForm, self).save(commit=commit)
return ArticleForm
class ArticleAdmin(admin.ModelAdmin):
exclude = ('owner',)
def get_form(self, request, obj=None, **kwargs):
if obj is None: # add
kwargs['form'] = make_add_form(request, self.form)
return super(ArticleAdmin, self).get_form(request, obj, **kwargs)
I have a UserAdmin, and I defined a UserProfileInline like this:
from ...
from django.contrib.auth.admin import UserAdmin as UserAdmin_
class UserProfileInLine(admin.StackedInline):
model = UserProfile
max_num = 1
can_delete = False
verbose_name = 'Profile'
verbose_name_plural = 'Profile'
class UserAdmin(UserAdmin_):
inlines = [UserProfileInLine]
My UserProfile model has some required fields.
What I want is to force the user not only to enter the username & repeat password, but also to enter at least the required fields so that the UserProfile instance is created and associated to the User that is being added.
If I enter anything in any field of UserProfileInline when creating the user, it validates the form without problem, but if I don't touch any field, it just creates the User and nothing happens with the UserProfile.
Any thoughts?
Check recent answer Extending the user profile in Django. Admin creation of users , you need to set the empty_permitted attribute of the form of the inline , to be False. Just like
class UserProfileForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args, **kwargs)
if self.instance.pk is None:
self.empty_permitted = False # Here
class Meta:
model = UserProfile
class UserProfileInline(admin.StackedInline):
form = UserProfileForm
Another possible solution could be to create your own Formset (that inherits from BaseInlineFormSet), like suggested in this link.
It could be something like that:
class UserProfileFormset(BaseInlineFormSet):
def clean(self):
for error in self.errors:
if error:
return
completed = 0
for cleaned_data in self.cleaned_data:
# form has data and we aren't deleting it.
if cleaned_data and not cleaned_data.get('DELETE', False):
completed += 1
if completed < 1:
raise forms.ValidationError('You must create a User Profile.')
Then specify that formset in the InlineModelAdmin:
class UserProfileInline(admin.StackedInline):
formset = UserProfileFormset
....
The good thing about this second option is that if the UserProfile model doesn't require any field to be filled, it will still ask for you to enter any data in at least one field. The first mode doesn't.