Django ModelAdmin get_form() doesn't set fields attribute - python

I've overriden get_form() in my ModelAdmin class:
def get_form(self, request, obj=None, **kwargs):
form = super(ModelAdmin, self).get_form(request, obj, **kwargs)
Now, if I add this line:
print form.fields
I get an:
AttributeError: type object 'FilerImageForm' has no attribute 'fields'
Why is this happening? Should the call to the super get_form() set the fields attribute on the form? If I am mistaken, how can I access fields on a form in a ModelAdmin class?

get_form returns class not instance and fields attribute is instance attribute. So, you have to instantiate form before accessing fields.
Definition from django/contrib/admin/options.py:
def get_form(self, request, obj=None, **kwargs):
"""
Returns a Form class for use in the admin add view. This is used by
add_view and change_view.
"""
update:
I need to intercept form field creation, not the view. I need to
change a field's value, not mess with a template's context. I don't
think add_view() is the appropriate place for this.
I think you can do it by overriding formfield_for_dbfield method:
def formfield_for_dbfield(self, db_field, **kwargs):
"""
Hook for specifying the form Field instance for a given database Field
instance.
If kwargs are given, they're passed to the form Field's constructor.
"""
formfield = super(MyModelAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == "field_you_are_looking_for":
# change formfield somehow here
# (or above, by passing modified kwargs in 'super' call)
return formfield

Related

How to add placeholder text to a Django Admin field

I'd like to add placeholder text to a field in the Django Admin change form. In a regular ModelForm you can do this by overriding the field's widget or by modifying self.fields['my_field'].widget in the ModelForm __init__() method. How do I do something similar for a Django Admin?
The documented way is to override get_form():
The base implementation uses modelform_factory() to subclass form,
modified by attributes such as fields and exclude.
If you look at the docs for modelform_factory you'll see that you can pass widgets as kwarg. So this should work:
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
kwargs['widgets'] = {
'name': forms.TextInput(attrs={'placeholder': 'e.g. John Doe'})
}
return super().get_form(request, obj, **kwargs)
or, if you want to be sure you're not overriding any widgets (if you're inheriting from a subclass of ModelAdmin):
kwargs['widgets'] = kwargs.get('widgets', {})
kwargs['widgets'].update({'name': ...})
Override the render_change_form() method on your ModelAdmin, which provides access to the form instance:
class Address(model.Model):
street = models.CharField(max_length=50)
class AddressAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, *args, **kwargs):
form_instance = context['adminform'].form
form_instance.fields['street'].widget.attrs['placeholder'] = 'Your street'
return super().render_change_form(request, context, *args, **kwargs)
This approach would be the same for other field attributes like attributes like autocomplete, autofocus, min, max, required, type or pattern. You also have access to context["original"] which provides the model instance, in case you'd like to change the behavior based on the model instance.
The source code is the best reference for this:
https://docs.djangoproject.com/en/2.2/_modules/django/contrib/admin/options/#ModelAdmin
This is a way of doing it without having to manually add placeholder text to each field:
admin.py
from django import forms
class MyModelAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, *args, **kwargs):
form_instance = context['adminform'].form
for key, field in form_instance.fields.items():
if isinstance(field.widget, (forms.TextInput, forms.EmailInput)):
field.widget.attrs.update({'placeholder': field.label})
return super().render_change_form(request, context, *args, **kwargs)
Another way to do this is:
class MyModelAdmin(model.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
#--> Get form
form = super().get_form(request, obj, **kwargs)
#--> Add placeholder for field
form.base_fields['the_field_name'].widget.attrs['placeholder'] = "My_Place_Holder_Text"
#--> Return form
return form
#---
#---
This is similar to the answer of dirkgroten. The advantage here is that there is no need to worry about the widget used for the field.

Django - how to use kwargs

I'm overriding the .save() method of a django model and I'm trying to pass an extra argument when saving:
View:
def form_valid(self, form):
response = super(DeliveryCreateView, self).form_valid(form)
self.object.save(owner=self.request.user)
return response
In the .save()
def save(self, *args, **kwargs):
owner = kwargs.pop('owner', None)
My problem is that owner always comes empty.
What am I doing wrong?
Assuming this is a standard create or update view, the superclass form_valid will already be calling the model save method, via the form. You'll either need to deal with that situation, or don't call super.

How to support all REST operations for an endpoint in django rest framework

I have a subscription model that looks like this
class Subscription(models.Model):
name = models.CharField(max_length=100)
quantity = models.IntegerField(max_length=20)
stripe_id = models.CharField(max_length=100)
user = models.ForeignKey(User)
I would like to create an endpoint that allows POST, PATCH, DELETE, GET
So I did the following things
views.py
class SubscriptionDetail(viewsets.ModelViewSet):
serializer_class = SubscriptionSerializer
permission_classes = (IsAuthenticated,)
queryset = Subscription.objects.all()
serializers.py
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = ('name','quantity', 'stripe_id')
def update(self, instance, validated_data):
print "In update"
#how do I write create and delete?
urls.py
subscription = SubscriptionDetail.as_view({
'patch': 'update'
})
url(r'^rest-auth/subscription/$', subscription, name='something'),
Questions
Using the above when I send a PATCH request, I get an error. How can I fix this?
Expected view SubscriptionDetail to be called with a URL keyword
argument named "pk". Fix your URL conf, or set the .lookup_field
attribute on the view correctly.
While sending the patch request I would also like to send an 'email' field which is not on the subscription model. Is this possible to do? I need the email field in the POST (create) operation so that I know which user the subscription belongs to.
The easiest way is to do it this way.
keep the models class the same
views.py
from rest_framework import viewsets
#impost serializer and model class for subscription
class SubscriptionViewSet(viewsets.ModelViewSet):
serializer_class = SubscriptionSerializer
def get_queryset(self):
queryset = Subscription.objects.all()
#if you need to get subscription by name
name = self.request.QUERY_PARAMS.get('name', None)
if name is not None:
queryset = queryset.filter(name=name)
return queryset
serializers.py
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = ('name','quantity', 'stripe_id')
# django will handle get, delete,patch, update for you ....
# for customization you can use def update or def create ... to do whatever you need
# def create(self, validated_data):
# you can handle the email here
# and something like subscription= Subscription (name=validated_data['name'],vendor=validated_data['quantity']...)
# subscription.save()
# it will save whatever you want
urls.py
#use the router to handle everything for you
from django.conf.urls import patterns, include, url
from rest_framework import routers
#import your classes
router = routers.DefaultRouter()
router.register(r'subscription', views.SubscriptionViewSet,base_name='subscription')
urlpatterns = patterns('',
url(r'^', include(router.urls)),
)
For the creation of an Object you must implement the create function as described in the official documentation, found here. For patching you could use the partial argument from within you view class:
SubscriptionSerializer(subscription, data={'something': u'another', partial=True)
For deletion of the a Subscription, that could be done when you get the delete call as so in your view class:
if request.METHOD == 'DELETE':
subscription = Subscription.objects.get(pk=pk)
subscription.delete()
See this tutorial for complete example
Further more I think that you should include the "id" field in the SubscriptionSerialiser Meta class, otherwise it will be difficult to do the updates/deletions. I hope this helped a little.
Cheers,
Tobbe
When you want to use a method that allow make these operations you have to use a #detail_route() where you can say as well which methods will you use, like in the docs is said:
#detail_route(methods=['post'])
def set_password(self, request, pk=None):
user = self.get_object()
serializer = PasswordSerializer(data=request.data)
...
So to be able to use them you should add the next decorator
#detail_route(methods=['post', 'patch'])
To add another parameters you can do it for the .save() parameter. You just have to indicate the name of this and them just override your .save() model to check if that email belongs or not to the user that is trying to do the subscription. Here I paste you what the Django Rest docs says:
" Passing additional attributes to .save()
...
You can do so by including additional keyword arguments when calling .save(). For example:
serializer.save(owner=request.user)
Here I leave you the link for more information:
http://www.django-rest-framework.org/api-guide/serializers/#passing-additional-attributes-to-save
Using the above when I send a PATCH request, I get an error. How can I fix this?
Expected view SubscriptionDetail to be called with a URL keyword
argument named "pk". Fix your URL conf, or set the .lookup_field
attribute on the view correctly.
The error is caused because unlike create request, patch/update require a pk to know which object to update. That is why you have to supply the pk value for it. So, your url for PUT, DELETE andPATCH must have at least named parameter like this -
subscription = SubscriptionDetail.as_view({
'patch': 'update'
})
url(r'^rest-auth/subscription/(?<pk>(\d+))$', subscription, name='something'),
an example url will be - rest-auth/subscription/10 where 10 is the pk or id of the object. Django Rest Framework will then load the object internally to be updated.
While sending the patch request I would also like to send an 'email' field which is not on the subscription model. Is this possible to do? I need the email field in the POST (create) operation so that I know which user the subscription belongs to.
To add custom parameters, first declare the property in serializer, it is better to keep it required=False, so that other request does not throw error -
class SubscriptionSerializer(serializers.ModelSerializer):
custom_field = serialiers.BooleanField(required=False)
class Meta:
model = Subscription
fields = ('name','quantity', 'stripe_id')
def update(self, instance, validated_data):
print "In update"
so far this is enough for the django rest framework to accept the field custom_field and you will find the value in update method. To get the value pop it from the attributes supplied by the framework like this -
def update(self, instance, validated_data):
custom_field = validated_data.pop('custom_field', None)
if custom_field is not None:
# do whatever you like with the field
return super().update(instance, validated_data)
# for python < 3.0 super(SubscriptionSerializer, self).update(instance, validated_data)
When you overrided (I don't know if that's the proper conjugation of overriding a method) the update method, you stopped the ability to PUT or PATCH and object. Your new method only prints out "In update" but doesn't save the instance. Look at the update method from the serializer.ModelSerializer object:
def update(self, instance, validated_data):
raise_errors_on_nested_writes('update', self, validated_data)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
Notice the last few lines where the instance is saved with the values and then returned. Remove your update method on the SubscriptionSerializer object. This let's your parent object's create, update, retrieve, and delete methods do their magic which supports PATCH and PUT updates. The next problem is that your urls.py is using the Django rather than the REST framework router. Change it to this:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'subscription', SubscriptionDetail)
That should solve the patch update problem.
I don't think you can add an email field in your patch method without the attribute on the subscription model. That's just a guess on my part, and I may be wrong. Does the email field map to anything on any object? Can you use a ForeignKey to map it?
I hope that works for you, good luck!
In view.py you just need set the class with:
class SubscriptionDetail(mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView):
and add this to fix .lookup_field :
def update(self, request, *args, **kwargs):
log.error("OBJ update kwargs= %s , data = %s" % (kwargs, str(request.data)))
pk = request.data.get('id')
if (kwargs.get('pk') is not None):
kwargs['pk'] = request.data.get('id')
self.kwargs['pk'] = request.data.get('id')
return super().update(request, *args, **kwargs)
and add support to methods do you want :
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
# def get(self, request, *args, **kwargs):
# return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
# def patch(self, request, *args, **kwargs):
# return self.partial_update(request, *args, **kwargs)
#
# def delete(self, request, *args, **kwargs):
# return self.destroy(request, *args, **kwargs)
only tweak that remains is get for list or get for retrieve on element but should be easy now add something if we have one pk we may call self.retrieve else we may call self.list

Django default queryset and widget for ModelForm custom field

I have a Site model, and I am trying to create a SiteSelectorField that extends django.forms.ModelMultipleChoiceField, that uses my custom SiteSelectorWidget and Site.objects.all() as the queryset
Without the custom form field, my forms.py code looks like this (and works):
sites = forms.ModelMultipleChoiceField(queryset=Site.objects.all(), widget=SiteSelectorWidget())
I would like to limit the arguments passed, so I can do this
sites = SiteSelectorField()
But when I create the SiteSelectorField class, as below, Django tells me "SiteSelectorField' object has no attribute 'validators"
class SiteSelectorField(forms.ModelMultipleChoiceField):
queryset = Site.objects.all()
widget = SiteSelectorWidget()
def __init__(self, *args, **kwargs):
pass
How can I specify a default queryset and widget for this field so they don't need to be passed?
Delete the def __init__ method and code. By putting "pass" inside there, you're overriding the default functionality of ModelMultipleChoiceField, which your class inherits from, that would utilize the queryset.
Edit:
Re-structure your __init__ method like so:
def __init__(self, *args, **kwargs):
if not 'queryset' in kwargs:
kwargs['queryset'] = Site.objects.all()
return super(SiteSelectorField, self).__init__(*args, **kwargs)

How do I access the request object or any other variable in a form's clean() method?

I am trying to request.user for a form's clean method, but how can I access the request object? Can I modify the clean method to allow variables input?
The answer by Ber - storing it in threadlocals - is a very bad idea. There's absolutely no reason to do it this way.
A much better way is to override the form's __init__ method to take an extra keyword argument, request. This stores the request in the form, where it's required, and from where you can access it in your clean method.
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(MyForm, self).__init__(*args, **kwargs)
def clean(self):
... access the request object via self.request ...
and in your view:
myform = MyForm(request.POST, request=request)
For what it's worth, if you're using Class Based Views, instead of function based views, override get_form_kwargs in your editing view. Example code for a custom CreateView:
from braces.views import LoginRequiredMixin
class MyModelCreateView(LoginRequiredMixin, CreateView):
template_name = 'example/create.html'
model = MyModel
form_class = MyModelForm
success_message = "%(my_object)s added to your site."
def get_form_kwargs(self):
kw = super(MyModelCreateView, self).get_form_kwargs()
kw['request'] = self.request # the trick!
return kw
def form_valid(self):
# do something
The above view code will make request available as one of the keyword arguments to the form's __init__ constructor function. Therefore in your ModelForm do:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
# important to "pop" added kwarg before call to parent's constructor
self.request = kwargs.pop('request')
super(MyModelForm, self).__init__(*args, **kwargs)
UPDATED 10/25/2011: I'm now using this with a dynamically created class instead of method, as Django 1.3 displays some weirdness otherwise.
class MyModelAdmin(admin.ModelAdmin):
form = MyCustomForm
def get_form(self, request, obj=None, **kwargs):
ModelForm = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
class ModelFormWithRequest(ModelForm):
def __new__(cls, *args, **kwargs):
kwargs['request'] = request
return ModelForm(*args, **kwargs)
return ModelFormWithRequest
Then override MyCustomForm.__init__ as follows:
class MyCustomForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(MyCustomForm, self).__init__(*args, **kwargs)
You can then access the request object from any method of ModelForm with self.request.
The usual aproach is to store the request object in a thread-local reference using a middleware. Then you can access this from anywhere in you app, including the Form.clean() method.
Changing the signature of the Form.clean() method means you have you own, modified version of Django, which may not be what you want.
Thank middleware count look something like this:
import threading
_thread_locals = threading.local()
def get_current_request():
return getattr(_thread_locals, 'request', None)
class ThreadLocals(object):
"""
Middleware that gets various objects from the
request object and saves them in thread local storage.
"""
def process_request(self, request):
_thread_locals.request = request
Register this middleware as described in the Django docs
For Django admin, in Django 1.8
class MyModelAdmin(admin.ModelAdmin):
...
form = RedirectForm
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
form.request = request
return form
I ran into this particular problem when customizing the admin. I wanted a certain field to be validated based on the particular admin's credentials.
Since I did not want to modify the view to pass the request as an argument to the form, the following is what I did:
class MyCustomForm(forms.ModelForm):
class Meta:
model = MyModel
def clean(self):
# make use of self.request here
class MyModelAdmin(admin.ModelAdmin):
form = MyCustomForm
def get_form(self, request, obj=None, **kwargs):
ModelForm = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
def form_wrapper(*args, **kwargs):
a = ModelForm(*args, **kwargs)
a.request = request
return a
return form_wrapper
The answer by Daniel Roseman is still the best. However, I would use the first positional argument for the request instead of the keyword argument for a few reasons:
You don't run the risk of overriding a kwarg with the same name
The request is optional which is not right. The request attribute should never be None in this context.
You can cleanly pass the args and kwargs to the parent class without having to modify them.
Lastly, I would use a more unique name to avoid overriding an existing variable. Thus, My modified answer looks like:
class MyForm(forms.Form):
def __init__(self, request, *args, **kwargs):
self._my_request = request
super(MyForm, self).__init__(*args, **kwargs)
def clean(self):
... access the request object via self._my_request ...
You can't always use this method (and its probably bad practice), but if you are only using the form in one view you could scope it inside the view method itself.
def my_view(request):
class ResetForm(forms.Form):
password = forms.CharField(required=True, widget=forms.PasswordInput())
def clean_password(self):
data = self.cleaned_data['password']
if not request.user.check_password(data):
raise forms.ValidationError("The password entered does not match your account password.")
return data
if request.method == 'POST':
form = ResetForm(request.POST, request.FILES)
if form.is_valid():
return HttpResponseRedirect("/")
else:
form = ResetForm()
return render_to_response(request, "reset.html")
fresh cheese from cheesebaker#pypi: django-requestprovider
I have another answer to this question as per your requirement you want to access the user into the clean method of the form.
You can Try this.
View.py
person=User.objects.get(id=person_id)
form=MyForm(request.POST,instance=person)
forms.py
def __init__(self,*arg,**kwargs):
self.instance=kwargs.get('instance',None)
if kwargs['instance'] is not None:
del kwargs['instance']
super(Myform, self).__init__(*args, **kwargs)
Now you can access the self.instance in any clean method in form.py
When you want to access it through "prepared" Django class views like CreateView there's a small trick to know (= the official solution doesn't work out of the box). In your own CreateView you'll have to add code like this:
class MyCreateView(LoginRequiredMixin, CreateView):
form_class = MyOwnForm
template_name = 'my_sample_create.html'
def get_form_kwargs(self):
result = super().get_form_kwargs()
result['request'] = self.request
return result
= in short this is the solution to pass request to your form with Django's Create/Update views.

Categories