I need to write a method in my ModelAdmin class that calls ModelAdmin.save_model(), without the user actually clicking on save.
(The reason I want to do this is that I set up some custom buttons in my Django Admin object view. They work but any modified form data is lost, I want to save the data in the form before running the actions connected to the button.)
Here's my code:
from django.contrib import admin
from .models import Object
class ObjectAdmin(admin.ModelAdmin):
def action_method(self, request, object):
self.save_model(request=request, obj=object, form=self.form, change=True)
admin.site.register(Object, ObjectAdmin)
This doesn't raise any errors but also doesn't save my data.
I'm guessing my problem might be to do with the form.
Any help?
class AuthorAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# Code Logic
obj.save()
Documentation
Related
I want to know the major difference between self.request.user (when using generic view View) and request.user (when using a user defined view), at times when I use django's generic View (django.views.generic import View) which has access to the django User model, I'd interact with the authenticated user as request.user and self.request.user without having any problem.
For instance, in a django views.py:
from django.contrib.auth import get_user_model
User = get_user_model()
class GetUserView(View):
def get (self, request, *args, **kwargs):
user_qs = User.objects.filter(username=request.user.username)
#Do some other stuffs here
class AnotherGetUserView(View):
def get(self, request, *args, **kwargs):
user_qs = User.objects.filter(username=self.request.user.username)
#Do some other stuffs here
I realize that the two queryset works fine but I can't still figure out the best to use.
There is no difference. Before triggering the get method, it will run the .setup(…) method [Django-doc] which will set self.request = request as well as self.args and self.kwargs to the positional and named URL parameters respectively. Unless you override this, self.request will thus always refer to the same object as request, and the documentation explicitly says that you should not override the .setup(…) method without making a call to the super method which will thus set self.request.
It however does not make much sense to do User.objects.filter(username=request.user.username): you already have the user object: that is request.user, so here you will only make extra database requests. user_qs is simply a queryset with one element: request.user. You can thus for example use request.user.email to obtain the email address of the logged in user.
You might want to use the LoginRequiredMixin [Django-doc] to ensure that the view can only be called if the user has been logged in, so:
from django.contrib.auth.mixins import LoginRequiredMixin
class GetUserView(LoginRequiredMixin, View):
def get (self, request, *args, **kwargs):
# …
I have a data model in which some fields can only be set initially per each instance of the class, and once set, they should never change. The only way that can be allowed to change such an object should be by deleting it and creating a new one.
Pseudo code:
from django.db import models
from django.core.exceptions import ValidationError
class NetworkConnection(models.Model):
description = models.CharField(max_length=1000)
config = models.CharField(max_length=1000)
connection_info = models.CharField(max_length=5000)
def clean(self):
from .methods import establish_connection
self.connection_info = establish_connection(self.config)
if not self.connection_info:
raise ValidationError('Unable to connect')
def delete(self):
from .methods import close_connection
close_connection(self.config)
super(NetworkConnection, self).delete()
As in the above code, the user should initially input both the config and the description fields. Then Django verifies the config and establishes some sort of network connection based on such configurations and saves its information to another field called connection_info.
Now since each object of this class represents something that cannot be edited once created, I need to hind the config field from the admin page that edits the object, leaving only the description field; However, the config field still needs to be there when adding a new connection. How do I do this?
The following is an example of my last admin.py attempt:
from django.contrib import admin
from .models import NetworkConnection
class NetworkConnectionAdmin(admin.ModelAdmin):
exclude = ('connection_info')
def change_view(self, request, object_id, extra_context=None):
self.exclude = ('config')
return super(NetworkConnection, self).change_view(request, object_id, extra_context)
admin.site.register(NetworkConnection, NetworkConnectionAdmin)
But unfortunately, it seems to hide the config field from the add page too. Not only the change page
You can achieve that with a custom method directly in your NetworkConnectionAdmin class:
from django.contrib import admin
from .models import NetworkConnection
class NetworkConnectionAdmin(admin.ModelAdmin):
exclude = ('connection_info')
def get_readonly_fields(self, request, obj=None):
if obj:
return ["config", "description"]
else:
return []
admin.site.register(NetworkConnection, NetworkConnectionAdmin)
I got it from the Django Admin Cookbook.
Turns out this could be done using the ModelAdmin.get_from function but if anybody has a better answer, please share it
Solution using get_from would be:
admin.py
from django.contrib import admin
from .models import NetworkConnection
class NetworkConnectionAdmin(admin.ModelAdmin):
exclude = ('connection_info')
def get_form(self, request, obj=None, **kwargs):
if obj:
kwargs['exclude'] = ('config')
return super(NetworkConnectionAdmin, self).get_form(request, obj, **kwargs)
admin.site.register(NetworkConnection, NetworkConnectionAdmin)
Edited: I am trying to update the value of a single field inside one of Django's objects. Here is the code:
class TodoCompleteView(generic.DetailView):
queryset = Todo.objects.all()
def get_object(self):
# Call the superclass
object = super(TodoCompleteView, self).get_object()
# Record the last accessed date
object.todo_completed = True
object.save()
# Return the object
return object
However, I keep getting an error:
TemplateDoesNotExist at /8/complete
list/todo_detail.html
How can avoid this? I simply want this view to flip a certain value in DB.
You inherit view from DetailView class, which by default is to view some models and not to change. Also, apparently, you use GET request to change the data. This is the wrong approach.
Alternatively I advise you to try to make inherit your view from SingleObjectMixin and View and manually create a handler for POST request.
I would rewrite your example like this:
from django.views.generic import View
from django.views.generic.detail import SingleObjectMixin
class TodoCompleteView(SingleObjectMixin, View):
model = Todo
def post(self, *args, **kwargs):
self.object = self.get_object()
self.object.todo_completed = True
self.object.save(update_fields=('todo_completed', ))
return HttpResponse(status=204)
P.S. you get the error, because DetailView subclassed from SingleObjectTemplateResponseMixin which tries to render the template called <model_name>_detail.html.
I have 2 separate models, Post and Comment. I use DetailView to display Post contents and I want to use a CreateView to display comment creation form on the same page. What is the cleanest way to go about that?
The only thing that comes to mind is to use custom view which both gets an object and processes comment form, but this looks too dirty:
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
if request.POST:
form = CommentForm(request.POST)
# do comment form processing here
return render(request, "post/post_detail.html", {
"object": post, "comment_form": form})
Is there any clean way to do this using class based views? Or just some way to decouple post display code from comment processing code?
It is possible to combine DetailView and CreateView. You use a class for DetailView and another class for CreateView, then you create a new class that inherits from View. This new class has a get and post method. The get method calls the DetailView while the post method calls the CreateView. Take note to use reverse_lazy for the success_url in CreateView. So basically your code should look something like this:
class PostView(DetailView):
# your code
pass ;
class CommentView(CreateView):
def get_success_url(self):
return reverse_lazy('post_detail', kwargs={'pk': self.get_object(Post.objects.all().pk})
class PostCommentView(View):
def get(self, request, *args, **kwargs):
view = PostView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs) :
view = CommentView.as_view()
return view(request, *args, **kwargs)
So your urls.py will point to
PostCommentView
I did an override of get_success_url because it will try to go to the detail view of the new comment which doesn't exist and is not what you want to do. So the override will take you to the DetailView of the post instead.
There is an explanation in the documentation.
One option would be to use the DetailView for the Post and a templatetag to display the comment form. Have the comment form submit to a Comment CreateView that redirects to the DetailView on success.
That said, it might get a little ugly if the form is invalid. In a pinch you can always call a DetailView or its methods from one of the CreateView methods. But IMO that introduces more coupling rather than less. Or you could have a separate utility function that you can call from the CreateView to display the Post if the comment form has errors.
Another option would be to use AJAX to process the comment form (in the separate CreateView) instead of a new page load.
In the end, regardless of language or framework, there's going to be a limit to how much one can decouple a view that needs to display one object type and create another.
am having problem with django custom signals not being able to see signals across application. I made a simple call in my
core/signals.py
from django.dispatch.dispatcher import Signal
# Signal-emitting code... emits whenever a file upload is received
# ----------------------------------------------------------------
upload_recieved = Signal(providing_args=['data'])
def upload_received_handler(sender, data, **kwargs):
print 'upload received handler'
print 'connecting signal'
upload_recieved.connect(upload_received_handler)
in core/models.py
import signals
[the model]
in blog/admin.py
from models import article, category, media
from django.contrib import admin
from libs.shared.core.tasks import Create_Audit_Record
from libs.shared.core import signals
class ArticleModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
upload_recieved.send(sender=self, data='ddd')
instance = form.save()
return instance
admin.site.register(article, ArticleModelAdmin)
admin.site.register(category)
admin.site.register(media)
this is what I did, but am getting an error in the runtime unable to see upload_received function. any ideas?
Regards,
You haven't imported the upload_recieved name into your admin.py.