I am using Django's forms to validate API PATCH requests. In the "view" (which I use in quotes because it isn't really a view directly, it is a restless Resource, but that should be irrelevant here) which handles this patch request, self.data contains a dictionary of changes to some of the fields of the License object. I want to instantiate a ModelForm with the instance of the object to be changed. Clearly, though, I am misunderstanding how this works. See below:
def handle_patch(self, pk):
license = License.objects.get(id=pk)
form = LicenseResourceForm(self.data, instance=license)
if not form.is_valid():
print(form.errors)
If I pass a few fields as data to the above function, form.errors complains about every other required field of the License model, meaning I'm clearly not understanding how setting an instance on a ModelForm works.
I added a few debug prints to Django's ModelForm code itself in the clean() method, and as it begins to do the cleaning process, I can see that self.instance is populated with the instance of License that I expect, which confuses me - the ModelForm object knows the instance, but isn't using it to "fill in the blanks" so to speak.
So what am I misunderstanding? I must be doing this wrong.
EDIT I realize that some of you may want to see the LicenseResourceForm itself, so here it is, including my debug print:
class LicenseResourceForm(ModelForm):
"""Form for License Resource create and change endpoints."""
class Meta(object):
model = License
fields = ['customer', 'service', 'enabled', 'not_valid_before', 'not_valid_after']
def clean(self):
try:
print(self.instance)
super().clean()
except Exception as e:
print(e)
Django forms aren't meant for API use and don't understand PATCH semantics. They are meant for the workflow of a user entering or changing data in a web form, which will always post all the data to the backend. Therefore, all fields listed in the fields attribute of the form will be checked against the data, and any missing fields will be validated as blank.
You could probably fix this by doing something clever to dynamically set the list of fields based on the data supplied, but really you should use the appropriate tool for validating your data. I don't know restless, but django-rest-framework has serializers which can be used for this.
Related
I'm working on the importing script that saves data from CSV to Django database. Saving process looks like this:
instance = ModelName(**kwargs)
instance.save()
# No errors reported
But when I try to edit and save some items using admin panel it shows me a message that some of the field values (like URL fields and custom validators) is not valid.
Question: Is there any way to validate model instance from the python code using Django admin validators?
The issue is save() does not validate by default.
To address this, you can call the model's full_clean method to validate before calling save.
So, under the hood,
This method calls Model.clean_fields(), Model.clean(), and
Model.validate_unique() (if validate_unique is True), in that order
and raises a ValidationError that has a message_dict attribute
containing errors from all three stages.
The code would look something like this:
instance = ModelName(**kwargs)
instance.full_clean() #Does validation here
instance.save()
I'm implementing a custom permission class for a Hyperlinked serializer that uses HyperlinkeRelatedFields and such.
The check is very roughly:
def has_permission(self, request,view):
if request.method in permissions.SAFE_METHODS:
return True
if not request.user.is_authenticated():
return False
if request.user.is_staff:
return True
# POST: 'author' is a URL due to serializer being Hyperlinked
# meaning we have to translate URL to model (like the serializer)
# to perform the check
if url_to_user(request.DATA['author']) == request.user:
return True
Given the comment in the code, it seems like it might be better to have the serializer do this check during validation, though that would shift concerns. It's almost like the validation class needs a method that is passed the new object before save() is called on it to check the POST/PUT was acceptable from a permissions point of view rather than cram HTTP/permissions related checks in to the validation in the serializer.
url_to_user is serialization specific, and checks in the validator portion of serializers would be request/http specific rather than just sanity/integrity checks of the new model.
It seems like a common thing to do so i'm curious which route others have taken and if there is a "more correct" approach i'm missing.
Thanks in advance.
Given that the comment says POST, I'll assume it's just the creation of a new object you're dealing with.
Since the author must be the request.user there's no need to have this as a writable field. I'd just set the object's author attribute to the current user in pre_save.
Similarly if you want to handle PUT you can limit the QuerySet to the current user too:
def get_queryset(self):
return MyClass.objects.filter(author=self.request.user)
(Looking at the request.method in permissions.SAFE_METHODS you may need to do this based on request method...)
That way a user cannot create an object not attached to themselves and cannot update one they do not already own.
However, as ever, TIMTOWTDI; there's nothing wrong with the permissions based route. (I'm not sure there's a final answer here.)
I'm trying to write a class-based view for Django which will utilise the same template each time, regardless of model. The intention is that I can then add a urls.py entry for each model, and not have to bother about a view or a template.
This will be used to display a form, and as the form field names are dependant on model type, the model needs to be examined and field names extracted at the view level, so they can be passed to the generic template. The template then generates the form based on field names and values of the object.
I've been really struggling with this. At the moment I'm working on overriding get_context_data as follows
def get_context_data(self, **kwargs):
context = kwargs
context_object_name = self.get_context_object_name(self.object)
if context_object_name:
context[context_object_name] = self.object
#add some custom stuff on too
tempdict = [(field, field.value_to_string(self)) for field in self.object._meta.fields]
#context.update({'datafields' : tempdict})
context.update({ 'blarg': 'tester!!'})
return context
The self.object._meta.fields bit is where I'm haivng the problems. I just can't get my head around how to access the current model. I'm doing this in a view, woud I have aany more luck in a mixin?
Thanks for your time.
O
I think you're going about this the wrong way. Django already knows how to create a form from a model, and forms know how to output themselves. So you can create a standard generic view using ModelFormMixin, there's no need to do anything clever to get form fields for a model's fields.
The only difficult bit is that you want one view to work for multiple models. So, rather than declaring the model explicitly on the view class, you'll need to work out some way of passing it dynamically - perhaps by overriding get_object.
If you're using django 1.3, class based views are included... Just use them and set the 'template_name' attribute to be your "common" name.
https://docs.djangoproject.com/en/1.3/topics/class-based-views/
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.
I know that you can prepopulate admin form fields based on other fields. For example, I have a slug field that is automatically populated based on the title field.
However, I would also like to make other automatic prepopulations based on the date. For example, I have an URL field, and I want it to automatically be set to http://example.com/20090209.mp3 where 20090209 is YYYYMMDD.
I would also like to have a text field that automatically starts with something like "Hello my name is author" where author is the current user's name. Of course, I also want the person to be able to edit the field. The point is to just make it so the user can fill out the admin form more easily, and not just to have fields that are completely automatic.
I know that you can prepopulate some values via GET, it will be something like this
http://localhost:8000/admin/app/model/add/?model_field=hello
I got some problems with date fields but, maybe this could help you.
I recently used Django's ModelAdmin.get_form method for this purpose.
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['my_field_name'].initial = 'abcd'
return form
Yout should be careful about side effects as you are manipulating the base_fields directly.
Django's built-in prepopulated_fields functionality is hardcoded to slugify, it can't really be used for more general purposes.
You'll need to write your own Javascript function to do the prepopulating. The best way to get it included in the admin page is to include it in the inner Media class of a custom Form or Widget. You'll then need to customize your ModelAdmin subclass to use the custom form or widget. Last, you'll need to render some inline Javascript along with each prepopulated field to register the onchange handler and tell it which other field to populate from; I would render this via the custom Widget. To make it nice and declarative you could use a custom ModelAdmin attribute (similar to prepopulated_fields), and override ModelAdmin.formfield_for_dbfield to create the widget and pass in the information about what field it should prepopulate from.
This kind of admin hacking is almost always possible, but (as you can tell from this convoluted summary) rarely simple, especially if you're making an effort to keep your code nicely encapsulated.
I tried a few of these answers and none of them worked. I simply wanted to prepulate a field with another field from a related model. Taking this answer as a starting point, I finally tried to manipulate the model instance object (here obj) directly and it worked for me.
class MyModelAdmin(models.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
if not obj.some_model_field:
obj.some_model_field = obj.related_model.prepopulating_model_field
return form
You can override the default django admin field by replacing it with a form field of your choice.
Check this :
Add custom validation to the admin
I would also like to have a text field
that automatically starts with
something like "Hello my name is
author".
Check out the docs at: http://docs.djangoproject.com/en/dev/ref/models/fields/#default
You could have a CharField() or TextField() in your model, and set this option, which will set the default text. 'default' can also be a callable function.
Something like:
models.CharField(max_length=250, default="Default Text")
The slug handling is done with javascript.
So you have to override the templates in the admin and then populate the fields with javascript. The date thing should be trivial, but I dont know how you should get the logged in users name to the script (not that I have thought very hard but you get the drift :).