Django Admin - Validating inlines together with main models - django

I have quite a complex validation requirement, and I cannot get Django admin to satisfy it.
I have a main model (django.contrib.auth.models.User) and several models which look like
class SomeProfile(models.Model):
user = models.OneToOneField(User)
# more fields
I want to check that, if the user belongs to some group, then it has the corresponding profile. So if user is in group Foo he should have a non empty FooProfile.
Where do I put this validation rule? I cannot put it in the model. Indeed, the user is not created yet when the form is validated, hence I cannot access his groups. So I need to resort to form validation. This is what I put:
class UserAdminForm(forms.ModelForm):
"""
A custom form to add validation rules which cannot live in the
model. We check that users belonging to various groups actually
have the corresponding profiles.
"""
class Meta:
model = User
def clean(self):
# Here is where I would like to put the validation
class FooInline(admin.TabularInline):
model = FooProfile
max_num = 1
class UserAdmin(admin.ModelAdmin):
model = User
form = UserAdminForm
inlines = [FooInline]
admin.site.register(User, UserAdmin)
My problem is that inside UserAdminForm.clean() I do not have access to the data posted inside the inlines. So I can tell whether the user is in group Foo by inspecting self.cleaned_data['groups'], but I have no way to tell whether a FooProfile was transmitted.
How do I check this validation requirement?
Edit:
I try to explain the issue better, because there has been a misunderstading in an answer.
I have an issue when I create a new user. The fact is that the profiles are mandatory (according to the groups). Say an admin creates a new user; then I have to add inlines in the admin form for the various GroupProfiles.
How do I check that the right profiles are not null? I cannot use the clean() method of the User model, because in there I cannot check what groups the user belongs to: it has not been created yet.
I can only access the information about the groups in the clean() method of the form - but there I do not have the information about the profiles, since this information is submitted trhough inlines.

1
well i have been looking around, how all this stuff works, and i found one question very similar here.
2
There are one way to get all the data at the same time maybe with this you can find the answer to your problem
class UserAdminForm(forms.ModelForm):
"""
A custom form to add validation rules which cannot live in the
model. We check that users belonging to various groups actually
have the corresponding profiles.
"""
class Meta:
model = User
def clean(self):
self.data # <--here is all the data of the request
self.data['groups']
self.data['profile_set-0-comments'] # some field
# some validations
return self.cleaned_data

Related

Retrieving multiple rows from seperate model in django relationships

I've been struggling with this puzzled for a few hours. Here's a schema of what I'm trying to do.
I have a user model and a profile model, it's a one-to-one relationship, but I'd like to be able to query a user and retrieve all the email addresses (from the User model) for users that share the same company (from the Profile Model). To be fair, my understanding of django is limited, but I went through the serializer relations guide and tried my hands at most approach described there, to no avail. At this point, I'm not even sure I'm on the right path.
So, my understanding of it is
From the user, I need to fetch the profile (a source='profile' approach may work)
From that profile, I need to retrieve the company
From that company, I need to retrieve all the user_id that belongs to that company
From those user_ids, I need to lookup the email fields of all those users
I need to also filter out the email address of the user making the request
Does this make any sense? At this point, I'm trying to accomplish all of that from the serializer, but was unsuccessful. Here are some snippets of code I tried, but I doubt any of them will point towards any form of solution, unfortunately.
class TeamEmailsSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['email']
class UserSerializer(serializers.ModelSerializer):
...
# only one of them was present at a time, but none gave any promising results
test_one = Profile.objects.filter(source=profile.company.id).values_list('user_id', flat=True)
test_one = serializers.RelatedField(source='profile.company.id', read_only=True)
test_one = TeamEmailsSerializer(many=True, read_only=True)
test_one = serializers.PrimaryKeyRelatedField(source='email', queryset=User.objects.filter())
class Meta:
model = User
fields = (
'test_one'
)
I'm grateful for any clue that may lead towards a solution.
First, you should add company FK on your user as well, it will make things much easier for you.
Then you can define a new method on User model:
class User(AbstractBaseUser):
...
def other_users_emails(self):
return self.company.users.exclude(pk=self.id).values_list('email', flat=True)
Then in your serializer add 'other_users_emails' to the fields list.
Alternatively you could modify to_representation method on your serializer and add 'other_users_emails' attribute directly there

How to set a model field based on the current user in a Django CreateView [duplicate]

I have a model named Domain which looks like this:
class Domain(models.Model):
"""
Model for storing the company domains
"""
user = models.ForeignKey(
User
)
host = models.CharField(
null=False, verbose_name="Host", max_length=128, unique=True
)
I'd like to use Django's generic views for doing CRUD operations on this. There is one field in this model that needs user input but the foreign key field doesn't need any user input. How can I exclude that field from the form that my generic view generates but assign it the value of the current authenticated user.
Thanks.
Have a look at Russel's answer to a similar question on the django-users group earlier this week.
Quoting the answer*:
Forms and Views solve different problems.
The View is solving the problem of "how do I handle this request and
convert it into a response?". The Form is solving the problem of "How
do I convert the POST data in this request into a model object (or a
change to a model object)?".
Very roughly, a view is doing the following:
View gets a request
View works out whether this is a GET or a POST
If its a POST, View asks the Form to turn the Post into a model change
Form returns success or failure
View responds to the success or failure of the Form.
View returns a response.
The functionality of the Form is a complete subset of the
functionality of the View -- and for this reason, it's a completely
interchangable internal component.
Now, in simple situations, it's possible for a View to guess all the
defaults for the form -- all it needs to know is that you're dealing
with a Foo model, and it can construct a default Foo ModelForm.
However, if you have more sophisticated form requirements, you're
going to need a customized Form.
We could have implemented this by exposing all the options of
ModelForm on the View class; but in order to keep everything clean, we
kept the ModelForm isolated, and provided the View with a way to
specify which Form class it's going to use.
So - to cover your use case of excluding fields, you define a
ModelForm that excludes the fields, then let the CreateView know the
form you want to use:
class CampaignForm(forms.ModelForm):
class Meta:
model = Campaign
exclude = ('user', 'name', 'content_inlined')
class CreateCampaignView(CreateView):
form_class = CampaignForm
template_name = "forms/create.html"
I'm guessing when you say "fix a values for a field", you mean setting
the values of user, name and content_inlined before you save the new
Campaign instance; to do this, you need to inject some extra code into
the form processing logic of the form:
class CreateCampaignView(CreateView):
form_class = CampaignForm
template_name = "forms/create.html"
def form_valid(self, form):
form.instance.user = ... (something meaningful.. e.g., self.request.user)
return super(CreateCampaignView, self).form_valid(form)
This overrides the default behavior when the form is valid, and sets
the extra values. The super() implementation of form_valid() will then
save the instance.
For the record, this could also be done by overriding the save()
method on the ModelForm -- however, if you do that, you lose the
request object, which you will need if you're trying to set the
instance values to something that is request-sensitive.
*the original answer set self.object.user instead of form.instance.user. This gives an AttributeError so I have changed it above.

How do I specify (1) an order and (2) a meaningful string representation for users in my Django application?

I have a Django application with users. I have a model called "Course" with a foreign key called "teacher" to the default User model that Django provides:
class Course(models.Model):
...
teacher = models.ForeignKey(User, related_name='courses_taught')
When I create a model form to edit information for individual courses, the possible users for the teacher field appear in this long select menu of user names.
These users are ordered by ID, which is of meager use to me. How can I
order these users by their last names?
change the string representation of the User class to be "Firstname Lastname (username)" instead of "username"?
Firsty, the order. You can define a default order of model using the nested Meta class. Check the ordering section.
Secondly, representation. You have to define a __str__()/__unicode__() methods for your model. They should return a string which represents an object. You can see an example in documentation. BUT, since User is a model from an outer module it may be hard to do it in that way.
You probably can:
monkey-patch the User model meta class during app initialization OR
subclass the User model, add Meta to the subclass and use it in place of the User OR
write a custom field / form template which uses objects attributes instead of calling str()
it all depends on the current case
Abstract
If you're only dealing with the admin, it's better not to tinker with the User model itself, as subclassing User can be a pain down the road (especially when / if other developpers are going to work on your project), but use the admin's customization options.
Solution
To solve your issue in the admin, you could use the ModelAdmin option raw_id_fields.
This will replace the <select> input with a widget that you can click to be redirected to your User admin and choose your user from.
From there, it's trivial to customize your User admin so that it:
Displays your users in a relevant order (ModelAdmin.ordering)
Displays the fields you're interest in (ModelAdmin.list_display)
Just remember than when you're registering your User admin, you need to use:
admin.site.unregister(User) #This!
admin.site.register(User, MyCustomUserAdmin)
Alternate solution
Alternatively, you can always use ModelAdmin.formfield_overrides, which is more powerful but more complicated.
Reference
You should look into the Django documentation for details on how to use those attributes.
You could achieve this by customizing form field, here the forms.ModelChoiceField
class CustomizedModelChoiceField(forms.ModelChoiceField):
def __init__(self, *args, **kwargs):
# change default ordering
super(CustomizedModelChoiceField, self).__init__(*args, **kwargs)
self.queryset = self.queryset.order_by('last_name')
def label_from_instance(self, obj):
# change representation per item
return u'{obj.first_name} {obj.last_name} ({obj.username})'.format(obj=obj)
Then use it in your form to replace default ModelChoiceField.
In Django Admin, it looks like
class CourseAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'teacher':
kwargs['form_class'] = CustomizedModelChoiceField
return super(CourseAdmin, self).formfield_for_dbfield(db_field, **kwargs)

does django models offer something similar to forms' clean_<fieldname>()?

I am trying to move all business-logic-related validations to models, instead of leaving them in forms. But here I have a tricky situation, for which I like to consult with the SO community.
In my SignupForm (a model form), I have the following field-specific validation to make sure the input email does not exist already.
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email
If I were to move this validation to the models, according to the official doc, I would put it in clean() of the corresponding model, ExtendedUser. But the doc also mentions the following:
Any ValidationError exceptions raised by Model.clean() will be stored
in a special key error dictionary key, NON_FIELD_ERRORS, that is used
for errors that are tied to the entire model instead of to a specific
field
That means, with clean(), I cannot associate the errors raised from it with specific fields. I was wondering if models offer something similar to forms' clean_<fieldname>(). If not, where would you put this validation logic and why?
You could convert your clean method into a validator and include it when you declare the field.
Another option is to subclass the model field and override its clean method.
However there is no direct equivalent of defining clean_<field name> methods as you can do for forms. You can't even assign errors to individual fields, as you can do for forms
As stated in the comment I believe you should handle this validation at the modelform level. If you still feel like it would be better to do it closer to the model, and since they can't be changed, I would advise a change directly at the db level:
ALTER TABLE auth_user ADD UNIQUE (email)
Which is the poor way to add the unique=True constraint to the User model without monkey patching auth.
As requested, I think that a good way to go about customizing different forms should be done by inheriting from a base modelform. A good example of this is found in django-registration. The only difference is that instead of the parent form inheriting from forms.Form you would make it a modelForm:
class MyBaseModelForm(ModelForm):
class Meta:
model = MyModel
You could then inherit from it and make different forms from this base model:
class OtherFormWithCustomClean(MyBaseModelForm):
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email

Access Hidden Inputs in Django forms class

Need help accessing Hidden inputs in a Form clean_data or clean function. Unable to find solution here and Django docs after about 1 hr of searching.
class AccountForm(forms.ModelForm):
class Meta:
model = Account
#action = forms.CharField(widget=forms.HiddenInput()) <--- also tried this
exclude = [
"a_account_number"
]
# Validate that there isn't already an account that exists with a similar company name during account creation
def clean_a_company_name(self):
logging.debug("Value of action %s") % self.data.__getitem__('action')
if Account.objects.filter( a_company_name = self.cleaned_data['a_company_name']).exists() and % self.data.__getitem__('action')== 'create':
logging.debug("In account views - form validation - clean_a_company - company already exists raising exception *****************")
raise forms.ValidationError(u"An organization with this name and owner already exists. Change the Organization name or edit Organization Account information instead of creating a new Organization Account")
return self.cleaned_data["a_company_name"]
Above gives a unicode error. I also tried:
%self.fields['action']
So you are trying to access action field in method for cleaning a_company_name?
You don't have access to other field in field's clean method. You should use form's clean method.
From django docs:
The Form subclass’s clean() method.
This method can perform any validation
that requires access to multiple
fields from the form at once. This is
where you might put in things to check
that if field A is supplied, field B
must contain a valid email address and
the like.
https://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other