I'm not talking about just custom fields to the form or static data, I'm talking about adding a section which actually has it's own code. Kind of a new entry in the fieldset but which introduces not a new field but some small reports on the users's activity.
Actually this questions stands for any model's change page. The Django docs show you how to overwrite the template for this page but what good is that without adding some python code also?
You can overrride default templates and default views.
Django has two different views and templates for admin record displaying. One for creating a new one and one for displaying an existing one and editing it. Related methods are:
Add Form Template and Add View for adding a new record
Change Form Template and Change View for displaying and changing an existing record
This is an example of how to prepare related override views (taken from Add/Change View link)
class MyModelAdmin(admin.ModelAdmin):
# A template for a very customized change view:
change_form_template = 'admin/myapp/extras/openstreetmap_change_form.html'
def get_osm_info(self):
# ...
pass
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['osm_data'] = self.get_osm_info()
return super(MyModelAdmin, self).change_view(request, object_id,
form_url, extra_context=extra_context)
You must check default django add and change templates from django source code (and maybe copying it as your new template and editing afterwards) to see how you can prepare your custom templates.
A final note, Never edit django template or vieew codes directly from source, since they are used by many different applications and and update to django source code might override your edit or may cause problems.
The Django admin is very extensible beyond overriding the templates.
Make sure you look at the ModelAdmin methods section in the documentation.
You can modify pretty much any behavior of the ModelAdmin.
You should also look at the custom form validation and ModelForms documentation, as a custom form for your model attached to its ModelAdmin gives you another (deeper, in most ways) level of customization.
Related
How do you do perform validation checks involving multi-inline forms in Django admin inlines?
For example, I have a simple Parent/Child model, with an admin interface showing the children in an inline table on the parent's admin change page.
Each child has a "name" field which must be unique.
On child model, I've implemented a clean() method to enforce this rule, raising a forms.ValidationError so the error is displayed in a user-friendly fashion in the admin UI. This method is called from the model's full_clean() method, which is called by Django admin during the validation step for each inline form. So, individually, if the user attempts to create a child record, that check caches the error.
However, since Django runs the validation for each inline table separately before saving the records, it doesn't cache duplicates in the new data. So if the user creates two new inline rows and enters duplicate names in each of those rows, they pass the validation check, but then when Django goes to actually save the records, it encounters the exception, which is now handled like a very user-unfriendly 500 error.
Is there an easy way to fix this? Looking through Django's code, I'm not seeing anything obvious in the _changeform_view() that houses most of the admin form validation logic.
Presumably, I'd override something on the inline's ModelForm, but even the clean method on that only validates the fields for a single record, not across multiple records.
I had a similar problem myself and spent quite a while on a solution.
I'm not sure if you found an answer (since it has been 5 months since you have asked) but either way I think that sharing my solution might be beneficial so here you go:
I tried to override the clean() method of various classes also but to no avail. Then I found this page (Customize Save In Django Admin Inline Form) which suggested overriding the save_new_objects and save_existing_objects methods on a CustomInLineFormSet class.
So in admin.py I added the following method to a CustomInLineFormSet class (or in your case this would be intended for the Child model):
class ChildInLineFormSet(BaseInLineFormSet):
def save_new_objects(self, commit=True):
saved_instances = super(ChildInLineFormSet, self).save_new_objects(commit)
if commit:
for instance in saved_instances:
instance.delete()
try:
ChildModel.objects.get(name=instance.name)
except ChildModel.DoesNotExist:
instance.save()
else:
saved_instances.remove(instance)
return saved_instances
Also, wherever you have declared your InLine class you must also add the definition for the formset field:
class ChildInLine(admin.StackedInline):
formset = ChildInLineFormSet #add this to whatever you already have
I hope this helps!
EDIT: I did a bit more digging on this: Using a custom formset is NOT necessary after all.
You can override the save_formset() method in the admin class and and should obtain the same result without having to save the models to the database:
class ParentAdmin(admin.ModelAdmin):
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
unique_names = []
for obj in formset.deleted_objects:
obj.delete()
for instance in instances:
if (instance.name) in unique_names:
instance.delete()
continue
unique_names.append(instance.name)
instance.save()
formset.save_m2m()
Big picture: I'd like my reverse method in get_absolute_url (see below) to return a url with a query parameter appended to it at the end, e.g. <url>?foo=bar. Further, I'd like bar to be specified by the POST request that triggered the call to get_absolute_url, either as an input to the form (but not a field represented by the model, something temporary) or as a url query parameter. I am easily able to access bar in my view using either method, but I can't seem to figure out how to access it in my model.
The motivation here is that my detail page splits up the fields from my model into different tabs using javascript (think https://www.w3schools.com/howto/howto_js_tabs.asp). When the user is updating the model, they choose which tab they want to update, and then the update template only renders the fields from the model which are related to that tab. More importantly, after the user submits the update, I want the detail page to know to open the specific tab that the user just edited.
(I understand how this works if the field is a part of the model; in get_absolute_url with parameters, the solution is pretty straightforward and involves using self.id. In my case though, bar is not a part of the model and I can't figure out how else to access it)
Some specifics: I have a model in my project called Context. I have implemented a generic DetailView and an update page for the model using a modelform called ContextForm and a generic UpdateView called ContextUpdate. Once the form is submitted, I redirect to the detail page using get_absolute_url in models.py:
def get_absolute_url(self):
return reverse("context:review",kwargs={"slug": self.slug})
My urlpatterns in urls.py looks something like:
urlpatterns = [
url(r'^(?P<slug>[-\w]+)$',views.ContextDetail.as_view(),name="review"),
url(r'^(?P<slug>[\w]+)/edit$',views.ContextUpdate.as_view(),name="edit"),
]
I am able to access this parameter in my UpdateView quite easily:
def post(self,request,**kwargs):
print (request.POST.get("bar")) #accessing input to form
print (request.GET.get("bar")) #accesssing url parameter
return super().post(request,**kwargs)
But when get_absolute_url is called inside the model, it seems I no longer have access to it.
Any suggestions for how to accomplish this? I want to use get_absolute_url (along with modelforms, generic views, etc.) so that I can follow Django conventions, but it seems like using get_absolute_url is making the functionality that I want difficult to accomplish. If the redirect to the detail view following the POST request were to happen inside my view, then I would know how to solve this (I think). Any thoughts or ideas would be greatly appreciated!
As you say, you can't access the request inside your get_absolute_url method. Therefore you should override get_success_url, from which you can access it.
def get_success_url(self):
return reverse(reverse("context:review", kwargs={"slug": self.object.slug}) + '?bar=%s' % self.request.GET.get('bar')
Or if you want to re-use get_absolute_url:
def get_success_url(self):
return self.object.get_absolute_url + '?bar=%s' % self.request.GET.get('bar')
The second option is DRYer but would break if get_absolute_url was changed to include a querystring like ?foo=foo.
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 use the following get_readonly_fields method to not allow editing of objects in django's admin interface:
def get_readonly_fields(self, request, obj=None):
if obj == None or request.user.is_superuser:
return self.readonly_fields
# marks all fields as readonly otherwise
fields = [f.name for f in self.model._meta.fields]
return fields
This works perfectly, but the save and save and continue editing still show up. They won't do anything since all fields are read-only.
Hence my question: Is there a way to hide these save buttons dependent on whether all fields are read-only or not? How could I implement this?
EDIT1:
I'm aware on how to override the admin/submit_line.html template, but what I would like to do instead, is to set the show_save, show_save_as_new to False if I only have read-only fields. How can I change these variable values?
In django/contrib/admin there is a file called submit_line.html which renders the buttons. To override them, in your templates directory, create a folder called admin, and in admin/submit_line.html you would modify it the way you want (based on certain rules). Please note that modifying it this way would affect every admin object-view page.
I need to create a form to admin-side with two fields
Number of code: integer
Value of code: float
How can I do that. This form is not related to any model.
You can implement your modelless form as explained in #levi's answer.
Then, you can place it in the admin site in a number of different ways, depending your needs:
Make instances of the form available to all templates via a context processor, and override the admin templates to have it rendered wherever you want. You can create a view for only processing the form.
Create a view for both rendering and processing the form in a unique place, and hook it up to the admin as explained in the old Django Book, you'll need to make sure the template for that view extends one of the admin's templates (admin/change_form.html may be a good choice).
from django import forms
class Your_Form(forms.Form):
number_code = forms.IntegerField()
value_code = forms.FloatField()