How to make a "create-only" non-editable field in django admin - python

im looking for some solution about make a "create-only" field on django admin using models.
I saw some questions before, but no one can answer the core question: the field should appear when the user are creating on admin panel, but i dont want to be able to edit.
models.py
class Fonte(Always):
source_slug = models.CharField(max_length=255)
admin.py
#admin.register(Fonte)
class FonteAdmin(admin.ModelAdmin):
readonly_fields = ['source_slug']
the "readonly_fields" solves the problem in the matter of editing in the future, but ends up forbidding when creating.
Problem: Im using this field to make a hash and i dont wanna this change evermore.. I thought about using a second field that would generate a hash on top of the editable one field in the creation, after that the field would be "dead", but that seems to me to be contrary to the 2 way of normalization.
Is there any more elegant way?

Override get_readonly_fields in your Admin class like that:
def get_readonly_fields(self, request, obj=None):
if obj:
return ['source_slug']
return []

Related

Is it possible to create new fields using django admin page interface?

Is it possible to create and delete new charfields or textareas through the Django admin page without harcoding them?
For example, I have a simple model, registered in Django admin page
class DocumentList(models.Model):
title = models.CharField(max_length=200)
def __str__(self):
return self.title
Obviously, it has only one charfield on admin page, something like:
DocumentList: [___________]
How can I add another one and delete her later if needed from Django admin page without actually hardcoding another charfield/textarea in models.py, to make it look like:
DocumentList: [___________]
*****************[___________]
Django models are not meant to be dynamically altered. You have to explicitly add the fields on your model, run migrations to have the fields created in your database backend, and reload your server process (./manage.py runserver does this automatically).
If you want to create a model that can hold an arbitrary amount of text strings instead of just one or a fixed amount, you need to use a many-to-many relation to another model.
You can use a custom form in the admin, either by using the form option of the get_form method. This is the documentation example for how you'd pass a custom form:
from django import forms
from django.contrib import admin
from myapp.models import Person
class PersonForm(forms.ModelForm):
class Meta:
model = Person
exclude = ['name']
class PersonAdmin(admin.ModelAdmin):
exclude = ['age']
form = PersonForm
You can add extra fields, as in any form.
I was wondering why you wanted this. Since you said in a comment it is to submit information to an API, you can also use an action, taking input from the user in an intermediate page.
EDIT: As became apparent in comments, the form needs to be dynamic for the user, and not when it is created. Therefore, the solution is using inlines, which once created and linked to the current model, allow the user to add any number of related forms to the current form.

Do a certain backend operation on clicking Django Admin save button

In a library system, I have the Userbooks (refers to books issued by a user) object registered with Django Admin. And when Django Admin creates a Userbooks object and saves it, the Book object (refers to books in library, also registered with Django Admin) associated with that UserBook with a one_to_one relationship, needs to have its boolean field 'is_issued' to be set to true. How can I do this backend action when Admin clicks the 'save' button?
My suggestion would be to either you use pre save signals or just override the save method to do whatever operation use want
class ModelB(models.Model):
def save(self):
# add logic to change is_issue value to True
super(ModelB, self).save()
Hope this helps.
In the question you specifically asked that this action should happen when the admin tries to save it from admin. The solution suggested by #pansul-bhatt does the same thing on Model save. Even the alternative(Handling pre-save signal) would do the same thing. So even if you save the model from anywhere else in the code you will set is_issued as True.
The better way to do it is to override the save_model on the UserbooksAdmin.
class UserBookAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.is_issued = True
obj.save()
This should be enough to solve your problem. But there are other hooks available with Django Admin.

Django admin many-to-many reversed?

The answer to question Django admin ManyToMany inline "has no ForeignKey to" error refers to the Django Admin documentation. The models given there are:
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, related_name='groups')
and the inline admin classes are:
class MembershipInline(admin.TabularInline):
model = Group.members.through
class PersonAdmin(admin.ModelAdmin):
inlines = [MembershipInline,]
class GroupAdmin(admin.ModelAdmin):
inlines = [MembershipInline,]
exclude = ('members',)
... which allows group membership to be managed from the Person page but not from the Group page. But what if the administrator wants to manage members only from the Group page? Getting rid of the exclude line would allow both pages to manage the relationship, but the Django documentation (probably incorrectly) says "you must tell Django’s admin to not display this widget". What they probably mean is that you "should" tell Django's admin not to display it - nothing bad will happen if you don't, but it's redundant.
So without changing the models, is it possible to exclude the membership widget from the Person page instead of from the Group page? Both obvious attempts:
class PersonAdmin(admin.ModelAdmin):
inlines = [MembershipInline,]
exclude = ('Group.members',)
and
class PersonAdmin(admin.ModelAdmin):
inlines = [MembershipInline,]
exclude = ('groups',)
(the second using the related_name from the model) fail with the error:
'PersonAdmin.exclude' refers to field 'groups' that is missing from the form.
Yes, the model could be changed to put the ManyToManyField under Person. But since it is a symmetric relationship, there is no logical reason why it could not be managed from either Person or Group (but not both) without having to change the database schema. Can Django Admin manage group membership from the group page and exclude it from the person page?
what if the administrator wants to manage members only from the Group
page?
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
pass
#admin.register(Group)
class GroupAdmin(admin.ModelAdmin):
pass
Django by default shows a Person m2m widget in the GroupAdmin. You correctly use the through model to get inlines, but the inlines are a separate definition not affected by the exclude. EDIT: Another simple way to put it is that you only specify the inlines on the Admin where you want them, no need to specify them on the opposite side's Admin.
Using inlines:
from core.models import Group, Person
class MembershipInline(admin.TabularInline):
model = Group.members.through
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
pass
#admin.register(Group)
class GroupAdmin(admin.ModelAdmin):
inlines = [MembershipInline, ]
exclude = ('members',)
(tested on Django 3.0.3)
You don't give a reference for this claim:
the Django documentation (probably incorrectly) says "you must tell Django’s admin to not display this widget".
so I can only refer to the current (1.10) documentation for Django. It currently says of ManyToMany fields in the admin:
Django displays an admin widget for a many-to-many field on the model that defines the relation (in this case, Group). If you want to use an inline model to represent the many-to-many relationship, you must tell Django’s admin to not display this widget - otherwise you will end up with two widgets on your admin page for managing the relation.
So, in response to your correct statement:
But since it is a symmetric relationship, there is no logical reason why it could not be managed from either Person or Group (but not both) without having to change the database schema.
the reason is that the many-to-many relationship has to be defined somewhere; you have chosen to define it on the Group model, so that determines the default admin behaviour. If you want to move it, then you'll need to do a database migration to make that happen.
If, on the other hand, you want this documented behaviour to be different without changing your use of it — you don't seem to be asking a question that fits at StackOverflow. Better to report a bug with the program at the project's bug tracker, asking for a change in the software's behaviour.

Auto-populating created_by field with Django admin site

I want to use the Django admin interface for a very simple web application but I can't get around a problem that should not be that hard to resolve ..
Consider the following:
class Contact(models.Model):
name = models.CharField(max_length=250, blank=False)
created_by = models.ForeignKey(User, blank=False)
I can't find a way to auto-populate the created_by field and make the Django admin aware of it. Most of the method I've seen implies overloading the Object's save method and pass it the request user. They all requires to build your custom views and/or forms.
Optimally the form to create new contacts in the admin site should not show the created_by field (which is quite easy) and auto-populate it with the current user (which seems harder than it should).
http://code.djangoproject.com/wiki/CookBookNewformsAdminAndUser
Involves implementing save methods on your ModelAdmin objects.
You need to specify a default for the field, in this case a method call that gets the current user (see the auth documentation to get the current user).
I really wanted a better solution than the ones I found elsewhere, so I wrote some middleware - implementation on the Populate user Id question. My solution requires no changes to any form or view.
I did it by overriding the save_model method in django admin. Since you mentioned that you wanted to use the Django admin interface. So, I think my solution will be the most appropriate one. The solution goes like this:
First register the Contact model in the admin by excluding the 'created_by' field
#admin.register(Contact)
class Notice(admin.ModelAdmin):
list_display = ('name', 'created_by')
exclude = ('created_by', )
Then override the save_model method in the same class Notice
def save_model(self, request, obj, form, change,):
obj.created_by = request.user
super().save_model(request, obj, form, change)

Django - How to prepopulate admin form fields

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 :).

Categories

Resources