I have the following models:
class Computer(models.Model):
...
class Demo(models.Model):
computers = models.ManyToManyField(Computer)
...
class Scenario(models.Model):
demo = models.ForeignKey(Demo)
...
class Setting(models.Model):
scenario = models.ForeignKey(Scenario)
computer = models.ForeignKey(Computer)
Basically a Demo uses multiple computers. A demo also has multiple scenarios. Each scenario has some settings and each setting configures a computer.
My problem is while using the django Admin site to add a scenario, after the user selects a demo in the drop down list and configures the settings for some computers, I need to validate that the computers in the settings are actually in the demo.
I've poured through the django documentation, online sites, and tried everything I can think of and still can't get this work.
I can't use the custom form validation because although I could get the 'demo' object from the cleaned_data in the scenario form, I can't seem to get access to the settings that get submitted with the form. If I do model level validation by overriding 'clean', that only works when I change a scenario not when I add a new one because the computer_set is empty for new ones.
Any help is greatly appreciated.
You could just add a custom form to your SettingInline (I'm assuming from your post that Setting is an inline for Scenario).
You mention you can't use form validation but I don't see a reason why you'd need access to every other setting. If you want access to the other settings (say validation that involves all submitted settings) I'd override the formset itself.
class SettingForm(forms.ModelForm):
class Meta:
model = Setting
def clean_computer(self):
computer = self.cleaned_data.get('computer')
if not self.instance.scenario.demo.computers.filter(computer=computer).count():
raise forms.ValidationError("Computer not in demo")
return computer
class SettingInline(admin.TabularInline):
model = Setting
form = SettingForm
Related
I have a model
class SomeModel(models.Model):
emails = ArrayField(models.EmailField(), default=list)
And let's say I have the following Serializer of the model:
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = ['emails']
The email field is not blank-able, i.e: It's required to set a value for it when submitting a Form of the model, or when making changes to its Admin page.
My understanding is that DRF relies as well on Django's internal machinery to validate whether emails is missing on the Serializer data or not. But the thing is that I can't find where (and when) this happens.
I've found that DRF is not calling the Model's clean() method anymore (link). But what baffles me is that changing the blank value on the field seems to have a direct impact on the Serializer. I have switched to blank=True, and then the Serializer would allow it to be saved without that field... Then I switched back to blank=False, and the Serializer would fail if emails is not present.
So do you have any idea of when and where DRF checks for a field's blank value?
Thanks!
As far as I know, it simply doesn't. Those are only used across forms and the django admin interface.
I always specify those things on the serializer level, by setting the appropiate arguments for my fields (doc), in this case it would be allow_blank.
I am building REST APIs with django, and the only case where the blank property on the model field catches me, is when fiddling around on the admin page.
However, there appears to be a package that could be of interest to you:
django-seriously.
I haven't used it, but it appears to call full_clean() on every save().
Of course, this has the disadvantage that you will probably loose DRFs nice error messages.
Basically in a popup (bootstrap) I would like to have all specified pre-populated fields from my model.
I found this code (https://groups.google.com/forum/#!searchin/django-rest-framework/HTMLFormRenderer/django-rest-framework/s24WFvnWMxw/hhmaD6Qw0AMJ)
class CreatePerformanceForm(forms.ModelForm):
model = Performance
fields = ('field1', 'field2')
class PerformanceCreateView(ListCreateAPIView):
serializer_class = PerformanceCreateSerializer
model = Performance
template_name = 'core/perform.html'
def get(self, request, format=None):
data = {'
form': CreatePerformanceForm()
}
return Response(data)
My question is the same.
Is there a way to create the form directly from the serializer so I don't have to create a Django form?
I looked at HTMLFormRenderer, but the DRF doc is quiet poor about this issue.
Thanks,
D
See this issue. Important part:
There are some improvements that could be made there [to HTMLFormRenderer], notably supporting error messaging against fields, and rendering the serializer directly into html without creating a Django form in order to do so [...]
So basically, HTMLFormRenderer also uses Django forms. Also, you are right, the documentation doesn't provide too much support for it. Even more, it seems that this renderer might soon change. See here. Quote:
Note that the template used by the HTMLFormRenderer class, and the context submitted to it may be subject to change. If you need to use this renderer class it is advised that you either make a local copy of the class and templates, or follow the release note on REST framework upgrades closely.
I know this doesn't help much, but for now there is no better way than the way you did it.
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
So I've got a UserProfile in Django that has certain fields that are required by the entire project - birthday, residence, etc. - and it also contains a lot of information that doesn't actually have any importance as far as logic goes - hometown, about me, etc. I'm trying to make my project a bit more flexible and applicable to more situations than my own, and I'd like to make it so that administrators of a project instance can add any fields they like to a UserProfile without having to directly modify the model. That is, I'd like an administrator of a new instance to be able to create new attributes of a user on the fly based on their specific needs. Due to the nature of the ORM, is this possible?
Well a simple solution is to create a new model called UserAttribute that has a key and a value, and link it to the UserProfile. Then you can use it as an inline in the django-admin. This would allow you to add as many new attributes to a UserProfile as you like, all through the admin:
models.py
class UserAttribute(models.Model):
key = models.CharField(max_length=100, help_text="i.e. Age, Name etc")
value = models.TextField(max_length=1000)
profile = models.ForeignKey(UserProfile)
admin.py
class UserAttributeInline(admin.StackedInline):
model = UserAttribute
class UserProfile(admin.ModelAdmin):
inlines = [UserAttibuteInline,]
This would allow an administrator to add a long list of attributes. The limitations are that you cant's do any validation on the input(outside of making sure that it's valid text), you are also limited to attributes that can be described in plain english (i.e. you won't be able to perform much login on them) and you won't really be able to compare attributes between UserProfiles (without a lot of Database hits anyway)
You can store additional data in serialized state. This can save you some DB hits and simplify your database structure a bit. May be the best option if you plan to use the data just for display purposes.
Example implementation (not tested)::
import yaml
from django.db import models
class UserProfile(models.Model):
user = models.OneToOneField('auth.User', related_name='profile')
_additional_info = models.TextField(default="", blank=True)
#property
def additional_info(self):
return yaml.load(self._additional_info)
#additional_info.setter
def additional_info(self, user_info_dict):
self._additional_info = yaml.dump(user_info_dict)
When you assign to profile.additional_info, say, a dictionary, it gets serialized and stored in _additional_info instead (don't forget to save the instance later). And then, when you access additional_info, you get that python dictionary.
I guess, you can also write a custom field to deal with this.
UPDATE (based on your comment):
So it appears that the actual problem here is how to automatically create and validate forms for user profiles. (It remains regardless on whether you go with serialized options or complex data structure.)
And since you can create dynamic forms without much trouble[1], then the main question is how to validate them.
Thinking about it... Administrator will have to specify validators (or field type) for each custom field anyway, right? So you'll need some kind of a configuration option—say,
CUSTOM_PROFILE_FIELDS = (
{
'name': 'user_ip',
'validators': ['django.core.validators.validate_ipv4_address'],
},
)
And then, when you're initializing the form, you define fields with their validators according to this setting.
[1] See also this post by Jacob Kaplan-Moss on dynamic form generation. It doesn't deal with validation, though.
This is a follow-up on How do you change the default widget for all Django date fields in a ModelForm?.
Suppose you have a very large number of models (e.g. A-ZZZ) that is growing with the input of other developers that are beyond your control, and you want to change the way all date fields are entered (i.e. by using jQueryUI). What's the best way to ensure that all date fields are filled out using that new widget?
One suggestion from the cited question was:
def make_custom_datefield(f):
if isinstance(f, models.DateField):
# return form field with your custom widget here...
else:
return f.formfield()
class SomeForm(forms.ModelForm):
formfield_callback = make_custom_datefield
class Meta:
# normal modelform stuff here...
However, is this possible to do where you don't have explicit ModelForm's, but url patterns come from models directly? i.e. your url config is likeso:
url(r'^A/?$', 'list_detail.object_list', SomeModelA)
where SomeModelA is a model (not a form) that's turned into a ModelForm by Django in the background.
At present in my system there are no Forms for each Model. The only point of creating forms explicitly would be to add the formfield_callback suggested in the prior solution, but that goes against DRY principles, and would be error prone and labour intensive.
I've considered (as suggested in the last thread) creating my own field that has a special widget and using that instead of the builtin. It's not so labour intensive, but it could be subject to errors (nothing a good grep couldn't fix, though).
Suggestions and thoughts are appreciated.
It sounds like you want to do this project-wide (ie: you're not trying to do this in some cases, but in ALL cases in your running application).
One possibility is to replace the widget attribute of the DateField class itself. You would need to do this in some central location... something that is guaranteed to be loaded by every running instance of the django app. Middleware can help with this. Otherwise, just put it in the __init__ file of your app.
What you want to do is re-assign the widget property for the forms.DateField class itself. When a new DateField is created, Django checks to see if the code specifies any particular widget in the field property definition. If not, it uses the default for DateField. I'm assuming that if a user in your scenario really defined a particular widget, you'd want to honour that despite the change to your global API.
Try this as an example of forcing the default to some other widget... in this case a HiddenInput:
from django import forms
forms.DateField.widget = forms.HiddenInput
class Foo(forms.Form):
a = forms.DateField()
f = Foo()
print f.fields['a'].widget
# results in <django.forms.widgets.HiddenInput object at 0x16bd910>