Using Django CreateView without Form to Create Object - django-models

I am using classed based views with django 1.3 and am trying to figure out how to create an object without using the form. I do not need any user input to create the object but I am still getting an error message that the template is missing. Below is my current view where I have tried to subclass the form_valid method but its not working. Any help would be appreciated.
class ReviewerCreateView(CreateView):
model = Reviewer
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.role = 2
self.object.save()
return HttpResponseRedirect(self.get_success_url())

A CreateView is a specialized view whose purpose is to display a form on GET and validate the form data and create a new object based on the form data on POST.
Since you don't need to display a form and process the form data, a CreateView is not the tool for your job.
You either need a plain old function-based view, or, if you prefer to use a class-based view, derive from View and override get() or post(). For example, adapting your sample code:
class ReviewerCreator(View):
def get(self, request, *args, **kwargs):
Reviewer(user=request.user, role=2).save()
return HttpResponseRedirect('/your_success_url/')

I don't believe a view needs to do anything explicit with a form if it does not need one.
You can instantiate a Reviewer object. It's just a python object.
class ReviewerCreateView(CreateView):
model = Reviewer
self.object.user = self.request.user
self.object.role = 2
self.object.save()
return HttpResponseRedirect(self.get_success_url())

Related

Wagtail ModelAdmin cleaning and validating fields that depend on each other

In Django you can add a clean method to a form to validate fields that depend on each other:
def clean_recipients(self):
data = self.cleaned_data['recipients']
if "fred#example.com" not in data:
raise ValidationError("You have forgotten about Fred!")
# Always return a value to use as the new cleaned data, even if
# this method didn't change it.
return data
How can I add a custom form with a clean method to Wagtail ModelAdmin?
Wagtail has the panels concept and dynamically builds the form. I can't find anything about overriding the form. There is information about customising the create and update views. Custom views seem a bit cumbersome.
I got an answer via the Wagtail Slack form #CynthiaKiser.
The base_form_class lets you set a WagtailAdminModelForm on the model.
from wagtail.admin.forms import WagtailAdminModelForm
class Foo(models.Model):
...
base_form_class = FooForm
class FooForm(WagtailAdminModelForm): # For pages use WagtailAdminPageForm
def clean(self):
data = self.cleaned_data['recipients']
if "fred#example.com" not in data:
raise ValidationError("You have forgotten about Fred!")
return data
All of the CRUD forms you encounter in Wagtail are just Django ModelForm instances underneath, so these Django docs are relevant (thanks #ababic)
An alternative to base_form_class is to specify a clean method on the model. It will be called by any form.
from django.core.exceptions import ValidationError
class Foo(models.Model):
...
def clean(self):
if "fred#example.com" not in self.recipients:
raise ValidationError(
{'recipients': _("You have forgotten about Fred!")}
)

Django REST Framework: slug or serializer

I am using Django Rest Framework for a project and I have a nested serializer like this:
class TopSerializer(serializers.ModelSerializer):
contact = (something goes here)
email = (something goes here)
For POST, PATCH, PUT, DELETE I want to specify these values with a slug. Suppose each class (Contact, Email) has a member called resource_id and that is my slug. For these methods I could use:
class TopSerializer(serializers.ModelSerializer):
contact = serializers.SlugRelatedField(read_only=False, slug_field='resource_id')
email = serializers.SlugRelatedField(read_only=False, slug_field='resource_id')
However, for GET I want to return the embedded objects too, so I could use:
class TopSerializer(serializers.ModelSerializer):
contact = ContactSerializer(read_only=True)
email = EmailSerializers(read_only=True)
So how do I specify in my serializer that contact can be either a slug or a serialized object? Is there a way to do this with just one serializer or must I have two different serializers and use the request.method in the view to select which serializer I use?
Or, should I use something like this:
class TopSerializer(serializers.ModelSerializer):
contact = ContactSerializer(read_only=True)
email = EmailSerializers(read_only=True)
contact_rid = serializers.SlugRelatedField(read_only=False,slug_field=resource_id,queryset=models.Contact.objects.all())
email_rid = serializers.SlugRelatedField(read_only=False,slug_field=resource_id,queryset=models.Email.objects.all())
This way I can use contact_rid and email_rid for POST/PATCH/PUT/DELETE and get contact and email fields back in GET.
Am I on the right track? Other suggestions?
Check out custom fields https://www.django-rest-framework.org/api-guide/fields/#custom-fields
You could define a custom serializer fields that overrides serializers.Field and overrride to_representation to return the fully serialized object and to_internal_value to mimic the behavior of a slugRelatedField.
You are on the right track!
Use one related field for write and another to read the full object is a good approach if you need more details for related objects.
You can also add to the slug field the flag write_only=True if you want the field is used only for write. However, checking this option will not hint selected objects when you are under an update route in Browseable API
Check this anwser

Enforcing values for some fields when using ModelForm

I have a Django app where users submit orders for payment. Clearly, security is important. I want to minimise the amount of code that I have to write, to avoid introducing any security holes, and ease maintenance.
The model is simple:
class Order(models.Model):
user = models.ForeignKey(User)
created = models.DateTimeField()
paid = models.DateTimeField(null=True, blank=True)
items = models.ManyToManyField(Item)
I'm using a CreateView to create instances of Order:
class OrderView(CreateView):
model = Order
form_class = OrderForm
I want to enforce values for certain fields in those instances. For example, I want the instance user field set to the current logged-in user. I don't want any possibility that the user can change the value of this field, so I don't want it to appear in the form at all. Therefore I use a custom ModelForm to remove these fields from the form:
class OrderForm(forms.ModelForm):
class Meta:
model = Order
# For security, we control exactly which fields are placed
# in the form, rather than excluding some:
fields = ('items',)
Now I want the newly created Order instances to have the user field set to the current logged-in user. I can't find any documentation about what is the best way to do this.
(A) I can override the form's save() method to modify the object before saving, but it feels like this code doesn't belong in the form, which doesn't know anything about the user field. I also don't have access to the request here, which I'd need to determine the current user. But it might look like this:
class OrderForm(forms.ModelForm):
def save(self, commit=True):
instance = super(OrderForm, self).save(commit=False)
instance.user = get_request_magic().user
if commit:
instance.save()
return instance
(B) I can override the view's form_valid method to save the object with commit=False, like a class-based version of this question. But I can't call the superclass method directly, because it saves the object with no way to disable commit, so I have to manually skip a generation of form_valid which is nasty. Apart from that complaint, this does look like the best way I've found so far:
class OrderView(CreateView):
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
return super(ModelFormMixin, self).form_valid(form)
(C) I could write a replacement for CreateView that adds a hook to allow objects to be changed before saving them. But that feels like more boilerplate and duplication.
(D) I can't provide an initial value, because there's no form field to put it in, so it will be ignored.
Any other ideas? If (B) the best option, is there any way around the hacky way of manually specifying which superclass' form_valid method I want to call?
Django user Charettes answered the question for me:
You can achieve this by overriding form_valid:
class OrderCreateViewMixin(CreateView):
def form_valid(self, form):
form.instance.user = request.user
return super(OrderCreateViewMixin, self).form_valid(form)
Which pointed me towards the right part of the documentation:
class AuthorCreate(CreateView):
form_class = AuthorForm
model = Author
def form_valid(self, form):
form.instance.created_by = self.request.user
return super(AuthorCreate, self).form_valid(form)
This is definitely the simplest and cleanest answer I've found so far. It doesn't require modifying the form in any way, although it does directly access its instance member which is a bit ugly. However, at least it's officially documented, so it's unlikely to break.
There are probably multiple approaches to this. I would do this:
Create a constructor in your form which takes the request:
def __init__(self, *args, **kwargs):
request = kwargs.pop('request', None)
super(OrderForm, self).__init__(*args, **kwargs)
self.request = request
When creating your form for POST processing, instantiate it as follows:
form = OrderForm(data=request.POST, request=request)
Now, in your save() method, you have access to the user on the request by referencing self.request.user and can set it accordingly on your model.
The way I've gone about handling this situation with CBVs, is to pass in an unsaved instance of the model to the form. This is how I've done it:
class OrderView(CreateView):
def get_form_kwargs(self):
self.object = Order(user=self.request.user)
return super(OrderView, self).get_form_kwargs()
Both CreateView and UpdateView will add instance to the form kwargs, setting it to the value of self.object.
The only other way, besides what you've already mentioned, is to construct your view class from the same elements that CreateView does, and then change the get and post methods to populate self.object there. I've done that when I have needed a lot of create views in my project:
class OrderView(SingleObjectTemplateResponseMixin, ModelFormMixin, ProcessFormView):
template_name_suffix = '_form'
def get(self, request, *args, **kwargs):
self.object = Order(user=request.user)
return super(OrderView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = Order(user=request.user)
return super(OrderView, self).post(request, *args, **kwargs)
Here is a more generalized version to be reused: https://gist.github.com/4439975

Django-nonrel form field for ListField

I'm experimenting with django-nonrel on appengine and trying to use a djangotoolbox.fields.ListField to implement a many-to-many relation. As I read in the documentation a ListField is something that you can use to make a workaround for djamgo-nonrel not supporting many-to-many relations.
This is an excerpt from my model:
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
So if I am getting this right I am creating a list of foreign keys to another class to show a relationship with multiple instances of another class
With this approach everything works fine ... No Exceptions. I can create `MyClass' objects in code and views. But when I try to use the admin interface I get the following error
No form field implemented for <class 'djangotoolbox.fields.ListField'>
So I though I would try something that I haven't done before. Create my own field. Well actually my own form for editing MyClass instances in the admin interface. Here is what I did:
class MyClassForm(ModelForm):
field = fields.MultipleChoiceField(choices=AnotherClass.objects.all(), widget=FilteredSelectMultiple("verbose_name", is_stacked=False))
class Meta:
model = MyClass
then I pass MyClassForm as the form to use to the admin interface
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
admin.site.register(MyClass, MyClassAdmin)
I though that this would work but It doesn't. When I go to the admin interface I get the same error as before. Can anyone tell what I am doing wrong here ... or if you have any other suggestions or success stories of using the ListField, SetField, etc. from djangotoolbox.fields in the admin interface it would be very much appreciated.
OK, here is what I did to get this all working ...
I'll start from the beginning
This is what what my model looked like
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
I wanted to be able to use the admin interface to create/edit instances of this model using a multiple select widget for the list field. Therefore, I created some custom classes as follows
class ModelListField(ListField):
def formfield(self, **kwargs):
return FormListField(**kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
"""
This is a custom form field that can display a ModelListField as a Multiple Select GUI element.
"""
widget = ListFieldWidget
def clean(self, value):
#TODO: clean your data in whatever way is correct in your case and return cleaned data instead of just the value
return value
These classes allow the listfield to be used in the admin. Then I created a form to use in the admin site
class MyClassForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyClasstForm,self).__init__(*args, **kwargs)
self.fields['field'].widget.choices = [(i.pk, i) for i in AnotherClass.objects.all()]
if self.instance.pk:
self.fields['field'].initial = self.instance.field
class Meta:
model = MyClass
After having done this I created a admin model and registered it with the admin site
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
def __init__(self, model, admin_site):
super(MyClassAdmin,self).__init__(model, admin_site)
admin.site.register(MyClass, MyClassAdmin)
This is now working in my code. Keep in mind that this approach might not at all be well suited for google_appengine as I am not very adept at how it works and it might create inefficient queries an such.
As far as I understand, you're trying to have a M2M relationship in django-nonrel, which is not an out-of-the-box functionality. For starters, if you want a quick hack, you can go with this simple class and use a CharField to enter foreign keys manually:
class ListFormField(forms.Field):
""" A form field for being able to display a djangotoolbox.fields.ListField. """
widget = ListWidget
def clean(self, value):
return [v.strip() for v in value.split(',') if len(v.strip()) > 0]
But if you want to have a multiple selection from a list of models normally you'd have to use ModelMultipleChoiceField, which is also not functional in django-nonrel. Here's what I've done to emulate a M2M relationship using a MultipleSelectField:
Let's say you have a M2M relationship between 2 classes, SomeClass and AnotherClass respectively. You want to select the relationship on the form for SomeClass. Also I assume you want to hold the references as a ListField in SomeClass. (Naturally you want to create M2M relationships as they're explained here, to prevent exploding indexes if you're working on App Engine).
So you have your models like:
class SomeClass(models.Model):
another_class_ids = ListField(models.PositiveIntegerField(), null=True, blank=True)
#fields go here
class AnotherClass(models.Model):
#fields go here
And in your form:
class SomeClassForm(forms.ModelForm):
#Empty field, will be populated after form is initialized
#Otherwise selection list is not refreshed after new entities are created.
another_class = forms.MultipleChoiceField(required=False)
def __init__(self, *args, **kwargs):
super(SomeClassForm,self).__init__(*args, **kwargs)
self.fields['another_class'].choices = [(item.pk,item) for item in AnotherClass.objects.all()]
if self.instance.pk: #If class is saved, highlight the instances that are related
self.fields['another_class'].initial = self.instance.another_class_ids
def save(self, *args, **kwargs):
self.instance.another_class_ids = self.cleaned_data['another_class']
return super(SomeClassForm, self).save()
class Meta:
model = SomeClass
Hopefully this should get you going for the start, I implemented this functionality for normal forms, adjust it for admin panel shouldn't be that hard.
This could be unrelated but for the admin interface, be sure you have djangotoolbox listed after django.contrib.admin in the settings.. INSTALLED_APPS
You could avoid a custom form class for such usage by inquiring for the model object
class ModelListField(ListField):
def __init__(self, embedded_model=None, *args, **kwargs):
super(ModelListField, self).__init__(*args, **kwargs)
self._model = embedded_model.embedded_model
def formfield(self, **kwargs):
return FormListField(model=self._model, **kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
widget = ListFieldWidget
def __init__(self, model=None, *args, **kwargs):
self._model = model
super(FormListField, self).__init__(*args, **kwargs)
self.widget.choices = [(unicode(i.pk), i) for i in self._model.objects.all()]
def to_python(self, value):
return [self._model.objects.get(pk=key) for key in value]
def clean(self, value):
return value

Django - Get instance pk inside form widget class

I have created a custom widget which has a link to some ajax functionality. In order to get this to work, I need to pass the pk of the instance I am currently editing. It seems that widgets have no way of accessing the current model instance (for good reason!) so I was wondering how I would get this information? Would I have to obtain it from the uri or is there a handy method I am overlooking which will give me what I need.
Thanks
You can override the __init__ method of the widget and pass it the the form instance. From the form instance you can get the pk if exists.
In this answer, they bind the form_instance to the widget on the init of the form.
django - how can I access the form field from inside a custom widget
Here is another question post talking about custom widgets accessing form instances.
Country/State/City dropdown menus inside the Django admin inline
In addition to chosen answer:
To use AdvancedModelChoiceField, and still render form via regular widgets, i subclassed django widget and overrided some methods.
first method be optgroups. Replace
for index, (option_value, option_label) in enumerate(chain(self.choices)):
with
for index, (option_value, option_label, obj) in enumerate(chain(self.choices)):
Then, we need to pass that obj to option.
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None, obj=None):
option = super(RadioSelect, self).create_option(name, value, label, selected, index, subindex, attrs)
option['obj'] = obj
return option
In widget template access obj like this:
{{ widget.obj.object_attr }}
And finally, we can use our custom widget and field in our form:
class MyForm(forms.ModelForm):
myfield = AdvancedModelChoiceField(
widget=MyWidget,
queryset=MyModel.objects.all(),
empty_label=None
)
Override the model field that you will use this widget with, and set your widget as default widget. When setting the default widget, instantiate the widget and pass whatever you need.
For example, I override the ForeignKey class to add a link after the select input.
The widget:
class SelectAdd(widgets.Select):
def __init__(self, related_name, related_create_url, attrs=None):
self.related_name = related_name
self.related_create_url = related_create_url
super().__init__(attrs=attrs)
def render(self, name, value, attrs=None, **kwargs):
rendered = super(SelectAdd, self).render(name, value, attrs, **kwargs)
return rendered + mark_safe(u'''
<span>Add new {} : {}</span>
'''.format(self.related_name, self.related_create_url))
And the model field:
class ForeignKeyAdd(ForeignKey):
def formfield(self, **kwargs):
db = kwargs.pop('using', None)
if isinstance(self.remote_field.model, six.string_types):
raise ValueError("Cannot create form field for %r yet, because "
"its related model %r has not been loaded yet" %
(self.name, self.remote_field.model))
defaults = {
'form_class': forms.ModelChoiceField,
'queryset': self.remote_field.model._default_manager.using(db),
'to_field_name': self.remote_field.field_name,
'widget': SelectAdd(
related_name=self.name,
related_create_url=get_crud_url(self.related_model, 'create')
),
}
defaults.update(kwargs)
return super(ForeignKey, self).formfield(**defaults)

Resources