I have a posting object that can be either accessed in the website or the admin panel. In both, the user is automatically assigned to the post when the user posts it. In the website area, it works fine. However, it does not work in the admin area. First, I tried just having the form input there. When I try to save the object and leave the input blank, it tells me the form has no value. Then, trying to see if I just put in a random value and see if it was overwritten, I did that. However, that random value was not overwritten with the current user. If I excluded the field, when I try to save the model I get an Integrity error saying the field author 'may not be NULL', so I'm assuming my save_model() function is not firing right at all. Now the code I'm using for this I've seen all over the internet and people claim for it to work, I don't know if it's just broken now or what. Here's my code:
from django.contrib import admin
from posting.models import Posting, Categories
class PostingAdmin(admin.ModelAdmin):
list_display = ("title","author", "article","date")
exclude = ('author',)
fieldsets = (
(None, {
'fields': ('title',)
}),
('Body', {
'fields': ('article',)
}),
)
def save_model(self,request,form,obj,change):
print 'ENTERING SAVE_MODEL FUNCTION'
if not change:
obj.author = request.user
print 'OBJ.AUTHOR:' + str(obj.author)
obj.save()
print "EXITING SAVE_MODEL FUNCTION"
admin.site.register(Posting, PostingAdmin)
I added this for information only as I came across this post which made me look deeper for an issue I was having that was similar...
To pre-populate an admin field in Django 1.11 from "request" data, see below example of a models "user" field being pre-populated with the logged in user:
admin.py
class PostingAdmin(admin.ModelAdmin):
def get_changeform_initial_data(self, request):
return {'user': request.user}
This populates the field when initially adding a new "Posting Admin" (model instance) before it has been saved for the first time.
You could override the ModelAdmin.get_form, by adding the request as an attribute of the newly created form class .
class EntryAdmin(admin.ModelAdmin):
form = EntryAdminForm
def get_form(self, request, *args, **kwargs):
form = super(EntryAdmin, self).get_form(request, *args, **kwargs)
form.request = request
return form
This works for me:
from django.contrib import admin
from page.models import Page
class PageAdmin(admin.ModelAdmin):
def get_form(self, request, *args, **kwargs):
form = super(PageAdmin, self).get_form(request, *args, **kwargs)
form.base_fields['author'].initial = request.user
return form
admin.site.register(Page, PageAdmin)
I know i am a bit late for posting this solution, but like me if anyone else come across you may try:
def save_model(self, request, obj, form, change):
if getattr(obj, 'author', None) is None:
obj.author = request.user
obj.save()
Related
I want to bulk_create models by importing csv data through django admin, with TextArea or FileField. I learned how to override template blocks, how to add new urls to django admin. But I have no idea how to solve my problem. I want to create custom admin page with my form. Pass data, parse it and bulk_create my model objects. Can you guys suggest the way how can I do this?
I found a snippet for this situation
from django.contrib import admin, messages
from django.http import HttpResponseRedirect
from django.shortcuts import render
from my_app.forms import CustomForm
class FakeModel(object):
class _meta:
app_label = 'my_app' # This is the app that the form will exist under
model_name = 'custom-form' # This is what will be used in the link url
verbose_name_plural = 'Custom AdminForm' # This is the name used in the link text
object_name = 'ObjectName'
swapped = False
abstract = False
class MyCustomAdminForm(admin.ModelAdmin):
"""
This is a funky way to register a regular view with the Django Admin.
"""
def has_add_permission(*args, **kwargs):
return False
def has_change_permission(*args, **kwargs):
return True
def has_delete_permission(*args, **kwargs):
return False
def changelist_view(self, request):
context = {'title': 'My Custom AdminForm'}
if request.method == 'POST':
form = CustomForm(request.POST)
if form.is_valid():
# Do your magic with the completed form data.
# Let the user know that form was submitted.
messages.success(request, 'Congrats, form submitted!')
return HttpResponseRedirect('')
else:
messages.error(
request, 'Please correct the error below'
)
else:
form = CustomForm()
context['form'] = form
return render(request, 'admin/change_form.html', context)
admin.site.register([FakeModel], MyCustomAdminForm)
from django import forms
class CustomForm(forms.Form):
# Your run-of-the-mill form here
Using a proxy model would save some typing:
class ImportCSVData(SomeModel):
class Meta:
proxy = True
#admin.register(ImportCSVData)
class MyCustomAdminForm(admin.ModelAdmin):
... as in accepted answer ...
I'm glad to say that since version 1.3.0 django-etc ships with etc.admin.CustomModelPage. So you may want to do something like:
from etc.admin import CustomModelPage
class BulkPage(CustomModelPage):
title = 'Test page 1' # set page title
# Define some fields.
my_field = models.CharField('some title', max_length=10)
def save(self):
# Here implement bulk creation using values
# from self fields attributes, e.g. self.my_field.
super().save()
# Register this page within Django admin.
BulkPage.register()
When I came across this answer, I was hoping to find a way to add a second form to the admin page for an existing model. The answer here gets you sort of close, but there is a much easier way to approach this.
For this example, I will assume the model we're working with is called Candle.
# Make a proxy class for your model, since
# there can only be one admin view per model.
class EasyCandle(models.Candle):
class Meta:
proxy = True
# Make a ModelAdmin for your proxy class.
#admin.register(EasyCandle)
class EasyCandleAdminForm(admin.ModelAdmin):
# In my case, I only want to use it for adding a new model.
# For changing an existing instance of my model or deleting
# an instance of my model, I want to just use the
# views already available for the existing model.
# So has_add_permission returns True while the rest return False.
def has_add_permission(*args, **kwargs):
return True
def has_change_permission(*args, **kwargs):
return False
def has_delete_permission(*args, **kwargs):
return False
# This replaces all the complicated stuff other
# answers do with changelist_view.
def get_form(self, request, obj=None, **kwargs):
return EasyCandleForm
# Finally, make whatever form you want.
# In this case, I exclude some fields and add new fields.
class EasyCandleForm(forms.ModelForm):
class Meta:
model = models.Candle
# Note, do NOT exclude fields when you want to replace their form fields.
# If you do that, they don't get persisted to the DB.
fields = "__all__"
vessel = forms.CharField(
required=True,
help_text="If the vessel doesn't already exist in the DB, it will be added for you",
)
I am using a Django CreateView and I wanted to set the success_url to the same view so that when the form is posted, it displays the same page and I can display the created object in addition to the form in case you want to add a new one. However, self.object is None because of this in BaseCreateView:
def post(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).post(request, *args, **kwargs)
I am concluding that a CreateView is not made to be redisplayed after success?
I was looking at the wrong place.
I have to override form_valid to not redirect to the URL (return HttpResponseRedirect(self.get_success_url()))
def form_valid(self, form):
self.object = form.save()
# Does not redirect if valid
#return HttpResponseRedirect(self.get_success_url())
# Render the template
# get_context_data populates object in the context
# or you also get it with the name you want if you define context_object_name in the class
return self.render_to_response(self.get_context_data(form=form))
I don't think you need the object being created to redirect to the same URL of the view. I'd use reverse:
class MyModelCreate(CreateView):
model = MyModel
success_url = reverse('path.to.your.create.view')
So it's 6 years later and I bumped into this problem while working on my project. In case anyone also faces this problem too in the near future here is a simple and easy fix but might not be recommended but it got the job done for me.
By the way I like using ClassBasedViews.
In your views.py file
class MyMode(CreateView):
model = my_model
success_url = '/path-to-webpage/'
So what I basically did was to hard-code in the path to the web-page under the success_url and that got the problem solved.
This works when you are not planning to change your URLpatterns anytime otherwise you will also have to change the URL in the views.py file too.
I'm using django-allauth, and trying to use one form to fill in initial data on the signup form. So, say there is a form on the home page where you fill in your email address, and that form posts to the signup form, and we just want the email address you filled in to be set as the initial data for the second form.
The way I've been trying to do it is by extending the SignupView class:
class MySignupView(SignupView):
def post(self, request, *args, **kwargs):
if not request.POST.get('submit'):
email_initial = request.POST.get("email_initial")
self.initial = {"email":email_initial}
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
else:
return super(MySignupView, self).post(self, request, *args, **kwargs)
I'm sure that the code after "if not request.POST.get('submit'):" is being run when I post to the signup form from the first form. I want email_initial to be set as the initial email on the signup form. The problem I'm having is that the second form is being validated as if it were submitted itself. This code:
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
Is exactly the same as the code that gets run on GET for the form. I'm able to set initial data when overriding the GET function, but not the POST function. Is there any way to just display the form with some initial data on POST, without validating the form?
I found a solution to this problem. Instead of posting the first form to the second one, I posted to an intermediary view, saved the email address in the session, then redirected to the allauth signup form. I extended the allauth signup form to check the session for this initial email address.
The first form action is:
{% url 'my_signup_email' %}
In urls.py:
url(r'^accounts/signupemail/', 'projectname.views.signup_email',name='my_signup_email'),
url(r'^accounts/signup/?', 'projectname.views.signup', name='my_signup'),
In views.py:
def signup_email(request):
request.session['email_initial'] = request.POST.get('email')
return redirect('my_signup')
class MySignupView(SignupView):
def get(self, request, *args, **kwargs):
self.initial = {"email":request.session.get('email_initial')}
return super(MySignupView, self).get(self, request, *args, **kwargs)
signup = MySignupView.as_view()
If anyone has any criticism of this solution, I would be interested to hear it.
I've looked at several questions here that looked similar, but none of them discussed the problem from the perspective of admin panel.
I need to check if user has permission to leave a field empty. I wanted to use request.user but I don't knot how to pass request from EntryAdmin to ModelForm. I wanted to do something like this:
class EntryAdminForm(ModelForm):
class Meta:
model = Entry
def clean_category(self):
if not self.request.user.has_perm('blog.can_leave_empty_category') and not bool(self.category):
raise ValidationError(u'You need to choose a Category!')
else:
return self.cleaned_data['category']
You could override the ModelAdmin.get_form, by adding the request as an attribute of the newly created form class (should be thread-safe).
Something along these lines:
class EntryAdmin(admin.ModelAdmin):
form = EntryAdminForm
def get_form(self, request, *args, **kwargs):
form = super(EntryAdmin, self).get_form(request, *args, **kwargs)
form.request = request
return form
Then the code in your question should work.
Let's say I have a site where Users can add Entries through admin panel. Each User has his own Category he is responsible for (each Category has an Editor assigned through ForeingKey/ManyToManyField).
When User adds Entry, I limit the choices by using EntryAdmin like this:
class EntryAdmin(admin.ModelAdmin):
(...)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'category':
if request.user.is_superuser:
kwargs['queryset'] = Category.objects.all()
else:
kwargs['queryset'] = Category.objects.filter(editors=request.user)
return db_field.formfield(**kwargs)
return super(EntryAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This way I can limit the categories to which a User can add Entry and it works perfect.
Now the tricky part: On the Entry changelist/action page I want to show only those Entries which belong to current User's Category. I tried to do this using this method:
def changelist_view(self, request, extra_context=None):
if not request.user.is_superuser:
self.queryset = self.queryset.filter(editors=request.user)
But I get this error:
AttributeError: 'function' object has no attribute 'filter'
This is strange, because I thought it should be a typical QuerySet. Basically such methods are not well documented and digging through tons of Django code is not my favourite sport.
Any ideas how can I achieve my goal?
Warning: This answer is from 2010, and is not useful for Django >= 1.8.
queryset is a method on ModelAdmin which returns a queryset. You need to override it on your EntryAdmin class.
def queryset(self, request):
qs = super(EntryAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
else:
return qs.filter(editors=request.user)
Changing the queryset will limit the Entries shown in the list view. You also need to override has_change_permission to ensure that the user has permission to edit the object on the individual object editing page. See the following blog post by James Bennett for further details:
http://www.b-list.org/weblog/2008/dec/24/admin/