Django admin change to_python output based on request - django

I'm wondering how to change the behavior of a form field based on data in the request... especially as it relates to the Django Admin. For example, I'd like to decrypt a field in the admin based on request data (such as POST or session variables).
My thoughts are to start looking at overriding the change_view method in django/contrib/admin/options.py, since that has access to the request. However, I'm not sure how to affect how the field value displays the field depending on some value in the request. If the request has the correct value, the field value would be displayed; otherwise, the field value would return something like "NA".
My thought is that if I could somehow get that request value into the to_python() method, I could directly impact how the field is displayed. Should I try passing the request value into the form init and then somehow into the field init? Any suggestions how I might approach this?
Thanks for reading.
In models.py
class MyModel(models.Model):
hidden_data = models.CharField()
In admin.py
class MyModelAdmin(models.ModelAdmin):
class Meta:
model = MyModel
def change_view(self, request, object_id, extra_context=None):
.... # Perhaps this is where I'd do a lot of overriding?
....
return self.render_change_form(request, context, change=True, obj=obj)

I haven't tested this, but you could just overwrite the render_change_form method of the ModelAdmin to sneak in your code to change the field value between when the change_view is processed and the actual template rendered
class MyModelAdmin(admin.ModelAdmin):
...
def render_change_form(self, request, context, **kwargs):
# Here we have access to the request, the object being displayed and the context which contains the form
form = content['adminform'].form
field = form.fields['field_name']
...
if 'obj' in kwargs:
# Existing obj is being saved
else:
# New object is being created (an empty form)
return super(MyModelAdmin).render_change_form(request, context, **kwargs)

Related

What is the Django way to add data to a form that is already instantiated?

Suppose you have a ModelForm to which you have already binded the data from a request.POST. If there are fields of the ModelForm that I don't want the user to have to fill in and that are therefore not shown in the form (ex: the user is already logged in, I don't want the user to fill a 'author' field, I can get that from request.user), what is the 'Django' way of doing it ?
class RandomView(View):
...
def post(self, request, *args, **kwargs):
form = RandomForm(request.POST)
form.fill_remaining_form_fields(request) ### How would you implement this ???
if form.is_valid():
...
I have tried adding the fields to the form instance (ex: self.data['author'] = request.user) but given its a QueryDict it is immutable so it clearly isn't the correct way of doing this.
Any suggestions ?
My bad, the Django documentation actually explains how to do this in https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/#selecting-the-fields-to-use (see the Note).

Can an UpdateView work without an object from the beginning?

Is there a way to use UpdateView without an object to update at first so I can pass it through AJAX? I want to create some sort of unique UpdateView for all the item where I can just select the item I want to update from a select.
Can I do this with UpdateView or I am going to need to code the view from scratch_
Yes. In your UpdateView you should override get method (or post method, depending on what exactly you want to do), you can choose what your view does after that, for example:
...
def get(self, request, **kwargs):
if 'id' in kwargs:
//perform update
else:
//do something else with AJAX
...
Yes you can do this by defining a get_object function in your view, and then accessing the relevant data in that function to determine which object is being updated.
https://docs.djangoproject.com/en/3.1/ref/class-based-views/mixins-single-object/#django.views.generic.detail.SingleObjectMixin.get_object
For example:
class SomeUpdateView(UpdateView):
...
def get_object(self):
#access info from the URL, like <pk> or <slug>
object_id = self.kwargs.get('pk')
#alternatively access the request.POST or request.GET data
some_info = self.request.POST.get('some_info')
#return a model instance based on some data in this function
return SomeModel.objects.get(pk=object_id)
...

automatically saved value in a UpdateView Django

i'm new in Django and i'm learning about the views and the methods and how they work, especially with this problem. The thing is that I would like to know how to automatically save a value of a field in my model after updating an object in a UpdateView, for example when I update an object, in this case a report where I can assign a person to do it, I would like to save a model value that shows the "status" and save the value of "assigned" or something like that, to know if the report was already assigned or not. I know there are methods and that maybe one of them could be done by overwriting the class, but I do not know how to apply it or which one to use.
For help this is a simple class of a UpdateViews that i'm using:
class reporteupdate(UpdateView):
model = reporte_fallo
form_class = ReporteAsignar
template_name = 'formulario/jefe_asignar.html'
success_url = reverse_lazy('formulario:reporte_listar_jefe')
and the field of the model that I would like to assign a value to is called status.
i'm waiting for your help, since I'm stuck with that doubt. Thanks!!!
the query dict will be changable after you create a copy of it in post method so you can do this:-
class SomeUpdateView(UpdateView):
model=your model
form_class=you form
def post(self, request, **kwargs):
request.POST = request.POST.copy()
request.POST['status'] = 'Assigned'
return super(SomeUpdateView, self).post(request, **kwargs)
You could perhaps set the status flag after the form has been successfully validated, by overriding the form_valid() method in your reporteupdate view:
class reporteupdate(UpdateView):
...
def form_valid(self, form):
# Call super() to save the model and return the success url
resp = super().form_valid(form)
# Set your status flag
self.object.status = 'assigned'
self.object.save()
return resp

super() save method on Django auth user's user change form

I am trying to edit django.contrib.auth.forms.UserChangeForm. Basically, auth_user's user edit page.
https://github.com/django/django/blob/master/django/contrib/auth/forms.py
According to source code, the form does not have a save() method, so it should inherit from forms.ModelForm right?
For full code, see here
class MyUserAdminForm(forms.ModelForm):
class Meta:
model = User
def __init__(self, *args, **kwargs):
super(MyUserAdminForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id: # username and user id
... the rest of the __init__ is setting readonly fields
.... some clean methods .....
def save(self, *args, **kwargs):
kwargs['commit'] = True
user = super(MyUserAdminForm, self).save(*args, **kwargs)
print user.username
print 'done'
return user
When I hit save, it said 'UserForm' object has no attribute 'save_m2m'. I've googled quite a bit, and tried to use add() but didn't work. What's causing this behaviour?
The thing is: the two print statements are printed. But the value never saved into database. I thought that the 2nd line would have saved once already.
Thanks
Remove the kwargs['commit'] = True line and see what happen.
Django Admin would invoke form.save_m2m(), which is hooked to the form when commit is False, here. The unconditional overriding of kwargs['commit'] = True would break the setattr of save_m2m() to form thus no attribute error is raised. The actual affected logic is here:
def save_form(self, request, form, change):
"""
Given a ModelForm return an unsaved instance. ``change`` is True if
the object is being changed, and False if it's being added.
"""
return form.save(commit=False)
You could find out that your version of form.save() overriding commit=False to commit=True unconditionally, thus Django Admin fails to continue as it believes form.save(commit=False) is invoked and thus form.save_m2m() needs to be called.
Refs the doc:
Another side effect of using commit=False is seen when your model has
a many-to-many relation with another model. If your model has a
many-to-many relation and you specify commit=False when you save a
form, Django cannot immediately save the form data for the
many-to-many relation. This is because it isn't possible to save
many-to-many data for an instance until the instance exists in the
database.
To work around this problem, every time you save a form using
commit=False, Django adds a save_m2m() method to your ModelForm
subclass. After you've manually saved the instance produced by the
form, you can invoke save_m2m() to save the many-to-many form data.

Django admin: How to populate a new object from GET variables?

In Django's admin, I've seen how I can set the fields of an 'add object' form using GET variables (e.g. /admin/app/model/add?title=lol sets the 'Title' field to 'lol').
However, I want to be able to do something along the lines of /admin/app/model/add?key=18 and load default data for my fields from an instance of another model. Ideally, I'd also like to be able to do some processing on the values that I populate the form with. How do I do this?
I managed to figure it out. Thankfully, Django allows you to replace a request's GET dict (which it uses to pre-populate the admin form). The following worked for me:
class ArticleAdmin(admin.ModelAdmin):
# ...
def add_view(self, request, form_url='', extra_context=None):
source_id = request.GET.get('source', None)
if source_id is not None:
source = FeedPost.objects.get(id=source_id)
# any extra processing can go here...
g = request.GET.copy()
g.update({
'title': source.title,
'contents': source.description + u"... \n\n[" + source.url + "]",
})
request.GET = g
return super(ArticleAdmin, self).add_view(request, form_url, extra_context)
This way, I obtain the source object from a URL parameter, do what I want with them, and pre-populate the form.
You can override method add_view of ModelAdmin instance. Add getting an object there, set object's pk to None and provide that object as an instance to the form. Object with pk == None will be always inserted as a new object in the database on form's save()