Conditionally override the model Django admin successfully saved message - django

I am overriding the save method on a Django model. There are some cases where I do not save the model. In these cases, I can't seem to be able to figure out how to conditionally override the "The citation 1111 was added successfully." message that is shown after returning back to the admin list interface (as opposed to the entry form interface).

I don't think you can override that just by overriding save Django's admin interface uses model forms and the messages framework.
I think something like this happens, it's more complicated than this but more or less:
models.py
class MyModel(models.Model):
foo = models.CharField(...)
bar = models.CharField(...)
def save(self, *args, **kwargs):
if self.foo == self.bar: # We only save if foo == bar
super(MyModel, self).save(*args, **kwargs)
forms.py (Django admin uses model forms, so this is an example)
class MyModelForm(ModelForm):
class Meta:
model = MyModel
views.py
def save(request):
if request.method == 'POST':
form = MyModelForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, 'MyModel was saved!.')
Now regardless of what form.save() actually did the message is still sent out anyway, we have no way of knowing if you saved or not in your overridden save method and this is probably whats happening in the django admin system.
An alternative would be to create a custom model form for the admin to use for this model and define a custom validation method, so the form doesn't validates unless foo == bar. Or you could override the save method on the form, you'll need to have a look around the django admin code, it probably is possible, just a bit trixy :p

Related

How to add custom page to django admin with custom form, not related to any Model?

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",
)

Readonly View for 10 Django Class Based Views

I have 10 Django Class Based Views and I want to display them read-only to the user.
I want the whole form to be read-only, not only some values. Submitting the form should be disabled on the client (HTML) and a second time on the server (POST not allowed).
Is there a MixIn or an other simple solution?
Here's a mixin that does two simple things:
Sets html attributes for all fields in form for disabled andreadonly.
Overrides the form_valid method of your CBV so that no model saving ever happens; instead, the template is rendered (just as if there was no submitted data). The user, this way, does not cause any action if they submitted the form.
Form field errors may appear next to disabled fields if you are rendering the full form in your template; solve this by either erasing the form's error dictionary or by rendering each field individually without errors.
from django.views.generic.edit import FormMixin, ModelFormMixin
class ReadOnlyModelFormMixin(ModelFormMixin):
def get_form(self, form_class=None):
form = super(ReadOnlyModelFormMixin, self).get_form()
for field in form.fields:
# Set html attributes as needed for all fields
form.fields[field].widget.attrs['readonly'] = 'readonly'
form.fields[field].widget.attrs['disabled'] = 'disabled'
return form
def form_valid(self, form):
"""
Called when form is submitted and form.is_valid()
"""
return self.form_invalid(form)
Extending this concept for a non-model FormView is pretty simple; inherit from class FormMixin instead. :)
To disallow POST requests in general for class-based views you could use the following mixin:
class DisallowPostMixin(object):
def post(self, request, *args, **kwargs):
return self.http_method_not_allowed(self, request, *args, **kwargs)
If you also want to disable certain form fields etc. you could add the get_form method from Ian Price's answer.
You can hack it through middleware. On request - check view name and request method (if post - redirect), on response - add input attrs in response.content. But mixin - best solution.

Use ModelAdmin form in any view

I've spent quite a bit of time setting up the ModelAdmin for my models, and I want to be able to basically reuse it elsewhere on my site, i.e., not just in the admin pages.
Specifically, I want to further subclass the ModelAdmin subclass I created and override some of the methods and attributes to be non-admin specific, then use that second subclass to create a form from a model in one of my ordinary (non-admin) views. But I'm not sure how to get the form out of the ModelAdmin subclass or how to bind it to a specific model.
So for instance, my admin.py looks something like this:
from django.contrib import admin
class MyAdmin(admin.ModelAdmin):
fields = ['my_custom_field']
readonly_fields = ['my_custom_field']
def my_custom_field(self, instance):
return "This is my admin page."
And this works real nice. Now in I want to subclass this as:
class MyNonAdmin(MyAdmin):
def my_custom_field(self, instance):
return "This is NOT my admin page!"
Now I want to use the MyNonAdmin in a view function to generate a form bound to a particular model, and use that form in a template. I'm just not sure how to do that.
Update
I found how to render the form, but it doesn't include any of the readonly_fields. Rendering the form looks like this:
def view_func(request, id):j
res = get_object_or_404(ModelClass, pk=int(id))
admin = MyNonAdmin(ModelClass, None)
form = admin.get_form(request, res)
return render(request, 'my_template.html', dict(
form=form,
))
But as I said, it only renders the form itself, not the other readonly_fields which is what I really care about.

django edit user and userprofile object

so i'm making a generic "accounts" page in django. I've used the django-registration plugin, and currently have a (djang-standard) User object, as well as a UserProfile and UserProfileForm object.
This is a question of style, or best-practices, i suppose. Is what i'm planning "right" or is there a "better/recommended/standard way" to do this?
What i'm planning on doing is creating the UserProfile from the request.user ie:
form = UserProfileForm(instance=User)
(and sending that form to the view), and in the UserProfileForm:
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
def __init__(self,*args,**kwargs):
super(UserProfileForm, self).__init__(*args, **kwargs)
if kwargs.has_key('instance'):
self.user = kwargs['instance']
where my UserProfile is pretty much like so:
class UserProfile(models.Model):
user = models.OneToOneField(User)
points = models.IntegerField(default=0) #how is the user going with scores?
and where User is of the django.contrib.auth.models variety.
Ok! The handling of the editing and saving will either be done via the mixin django stuff or, more likely because i haven't read up on mixins my own user-defined view that handles post and gets. But ignoring that - because i'm sure i should be using the mixins - is the above "right?" or are there suggestions?
cheers!
Take a look at user profiles on the django docs, the basics are listed there. You should also take a look at using a form in a view.
Some specific feedback:
You got the UserProfile model right, but you have to create an instance of one every time a new user is added (either through the admin interface or programmatically in one of your views). You do this by registering to the User post_save signal:
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
You should init the ModelForm with an instance of the UserProfile, not User. You can always get the current user profile with request.user.get_profile() (if you define AUTH_PROFILE_MODULE in settings.py). Your view might look something like this:
def editprofile(request):
user_profile = request.user.get_profile()
if request.method == 'POST':
form = UserProfileForm(request.POST, instance=user_profile)
if form.is_valid():
form.save()
return HttpResponseRedirect('/accounts/profile')
else:
form = UserProfileForm(instance=user_profile)
# ...
No need for the init override in your ModelForm. You will be calling it with a UserProfile instance, anyway. If you want to create a new user, just call the User constructor:
user = User()
user.save()
form = UserProfileForm(instance = user.get_profile())
# ...

How to clean form data depending on logged-in user in django admin panel?

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.