I need a way to have an intermediate page shown when I´ve saved a model in django admin.
What I want to accomplish is after "saving" a model, show a page with all the attributes of the model lined up and then have a button that says Print. I used to solve this with Jquery dialog div when clicking save. That meant that I showed the settings print view before actually saving the model but I need the model to validate first now.
Its like the way that the "delete model" action is implemented. I just can´t seem to find out where to start looking though.
Edit:
I´ve started looking in the django.contrib.admin.options.py for the response_change and response_add methods. Not sure how to override them though. And its only needed for one specific model so its not generic. Also I´ve discovered the list of templates in the Class ModelAdmin. Still not sure about how to proceed without hacking the admin to bits.
Edit 2:
Added my working solution down below.
You could create a form that had an extra step of validation for the 'are you sure' step.
Given this model in our models.py:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=100)
Add a form in forms.py:
from django import forms
from .models import Person
class PersonForm(forms.ModelForm):
i_am_sure = forms.BooleanField(required=False, widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
super(PersonForm, self).__init__(*args, **kwargs)
if self.errors.get('i_am_sure'):
# show the 'are you sure' checkbox when we want confirmation
self.fields['i_am_sure'].widget = forms.CheckboxInput()
def clean(self):
cleaned_data = super(PersonForm, self).clean()
if not self.errors:
# only validate i_am_sure once all other validation has passed
i_am_sure = cleaned_data.get('i_am_sure')
if self.instance.id and not i_am_sure:
self._errors['i_am_sure'] = self.error_class(["Are you sure you want to change this person?"])
del cleaned_data['i_am_sure']
return cleaned_data
class Meta:
model = Person
If you want to use this with Django admin. specify this form in your admin.py:
from django.contrib import admin
from .forms import PersonForm
from .models import Person
class PersonAdmin(admin.ModelAdmin):
form = PersonForm
admin.site.register(Person, PersonAdmin)
Note however that there's a bug with hidden inputs on Django admin forms. There's a solution to that on this Stack Overflow question.
You can add views and urls to your ModelAdmin and overwrite your modeladmin add view to redirect accordingly.
class MyModelAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super(MyModelAdmin, self).get_urls()
my_urls = patterns('',
(r'^my_view/$', self.my_view)
)
return my_urls + urls
def my_view(self, request):
# custom view which should return an HttpResponse
pass
So, after a bit of coding I got it to work.
My modeladmin looks simular to this now
class MyModelAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super(MyModelAdmin, self).get_urls()
my_urls = patterns('',
(r'^my_view/$', self.my_view)
)
return my_urls + urls
def my_view(self,request,pk):
from django.shortcuts import render_to_response
from django.template import RequestContext
object = Model.objects.get(pk=pk)
model_dict = model_object.__dict__
return render_to_response('admin/app_name/model/model_view.html',locals(),context_instance=RequestContext(request))
#csrf_protect_m
#transaction.commit_on_success
def add_view(self, request, form_url='', extra_context=None):
"The 'add' admin view for this model."
model = self.model
opts = model._meta
if not self.has_add_permission(request):
raise PermissionDenied
ModelForm = self.get_form(request)
formsets = []
inline_instances = self.get_inline_instances(request)
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES)
if form.is_valid():
new_object = self.save_form(request, form, change=False)
form_validated = True
else:
form_validated = False
new_object = self.model()
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(data=request.POST, files=request.FILES,
instance=new_object,
save_as_new="_saveasnew" in request.POST,
prefix=prefix, queryset=inline.queryset(request))
formsets.append(formset)
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, False)
self.save_related(request, form, formsets, False)
self.log_addition(request, new_object)
log.info('The new object has %s id' % new_object.id)
return HttpResponseRedirect('/admin/draws/contest/contest_view/%s' % new_object.id) <-- changed to my new one
.................
.................
Created a html template in templates/admin/app_name/model_view.html and that is it!
Related
I have a Django blog where the users can set the status of each post (active, inactive) when creating them. I am using Django's generic class-based views and would like to redirect the user to their created post only when the status is set to "active", otherwise they should be redirected to the homepage. How can I retrieve this submitted info from the form to create an if statement? For example: if status == "a"...
views.py
from .models import Listing
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic import CreateView
class PostCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = Post
fields = ['title', 'status', 'content']
template_name = 'blog/post-form.html'
success_message = 'Your post has been created.'
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
I think "redirect" is not valid withing the method (form_valid) so you should definitely put something like def post, and get the value in request.data. I'm going to try showing you a code that is working for me in production. You let me know if it was the right response.
class LoginFormView(FormInvalidMessageMixin, LoginView):
template_name = "clientes/login.html
def post(self, request, *args, **kwargs):
username = self.request.POST.get('username')
password = self.request.POST.get('password')
tipo_cuenta = self.request.POST.get('tipo-cuenta')
tipo_cuenta = tipo_cuenta.strip()
user = authenticate(username=username, password=password)
if user:
if has_group(user, 'admin'):
messages.success(request, _('Not for admin'))
return redirect('/login')
cliente = Usuario.objects.filter(usuario=user).first()
sucursal = Sucursal.objects.filter(usuario=user).first()
empleado = Empleado.objects.filter(usuario=user).first()
if tipo_cuenta == 'Punto afiliado':
login(self.request, user)
return redirect('/envios')
I would like to create a mutli-step form in Django that only submits the data for processing at the end of all the steps. Each step needs to be able to access and display data that we entered in previous step(s).
Is there a way to do this with Django? Django's Form-Wizard can't handle this basic functionality.
Of course there's a way to do this in Django.
One way is to hold your values in session until you submit them at the end. You can populate your forms using values held in session if you return to previous step.
With some searching, you may find an app that someone has already written that will do what you want, but doing what you need isn't hard to do with Django, or any other framework.
Example, ignoring import statements:
#models/forms
class Person(models.Model):
fn = models.CharField(max_length=40)
class Pet(models.Model):
owner = models.ForeignKey(Person)
name = models.CharField(max_length=40)
class PersonForm(forms.ModelForm):
class Meta:
model = Person
class PetForm(forms.ModelForm):
class Meta:
model = Pet
exclude = ('owner',)
#views
def step1(request):
initial={'fn': request.session.get('fn', None)}
form = PersonForm(request.POST or None, initial=initial)
if request.method == 'POST':
if form.is_valid():
request.session['fn'] = form.cleaned_data['fn']
return HttpResponseRedirect(reverse('step2'))
return render(request, 'step1.html', {'form': form})
def step2(request):
form = PetForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
pet = form.save(commit=False)
person = Person.objects.create(fn=request.session['fn'])
pet.owner = person
pet.save()
return HttpResponseRedirect(reverse('finished'))
return render(request, 'step2.html', {'form': form})
We'll assume that step2.html has a link to go back to step1.html.
You'll notice in the step1 view I'm pulling the value for fn from session that was set when the form was saved. You would need to persist the values from all previous steps into the session. At the end of the steps, grab the values, create your objects and redirect to a finished view, whatever that might be.
None of this code has been tested, but it should get you going.
You can easily do this with the form wizard of django-formtools. A simple example would be something like the following.
forms.py
from django import forms
class ContactForm1(forms.Form):
subject = forms.CharField(max_length=100)
sender = forms.EmailField()
class ContactForm2(forms.Form):
message = forms.CharField(widget=forms.Textarea)
views.py
from django.shortcuts import redirect
from formtools.wizard.views import SessionWizardView
class ContactWizard(SessionWizardView):
def done(self, form_list, **kwargs):
do_something_with_the_form_data(form_list)
return redirect('/page-to-redirect-to-when-done/')
urls.py
from django.conf.urls import url
from forms import ContactForm1, ContactForm2
from views import ContactWizard
urlpatterns = [
url(r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
]
I want to hide field name when user is creating new object, but it must be visible if user wants to edit this object.
I tried exclude method but it makes field invisible when i try to edit this field. for example i want to hide status field.
class Toys(BaseModel):
name = models.CharField(max_length=255)
tags = models.ManyToManyField(Tag, related_name='Item_tags')
price = models.CharField(max_length=255)
status = models.BooleanField(default=False)
In the model admin that you register in your admin.py file you can overload the get_form(self, request, obj=None, **kwargs) method. As you can see it takes the obj argument, it is None only on add (not None on change).
From there you could mess around with the form to exclude the form field "name" from it only if the obj is None.
In Django 1.10 that method is in django.contrib.admin.options.ModelAdmin.get_form.
EDIT 1 (this is by far not the best solution)
I can't give you a full solution here, but you can start with something like:
# admin.py
from django.contrib import admin
from models import Toys
class ToysModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
# all the code you have in the original
# django.contrib.admin.options.ModelAdmin.get_form
# up to the last try except
if obj is not None:
defaults['fields'] = ('tags', 'price', 'status', )
try:
return modelform_factory(self.model, **defaults)
except FieldError as e:
raise FieldError(
'%s. Check fields/fieldsets/exclude attributes of class %s.'
% (e, self.__class__.__name__)
)
admin.site.register(Toys, ToysModelAdmin)
EDIT 2 (this is a better example)
# admin.py
from collections import OrderedDict
from django.contrib import admin
from models import Toys
class ToysModelAdmin(admin.ModelAdmin):
# this is not perfect as you'll need to keep track of your
# model changes also here, but you won't accidentally add
# a field that is not supposed to be editable
_add_fields = ('tags', 'price', 'status', )
def get_form(self, request, obj=None, **kwargs):
model_form = super(ToysModelAdmin, self).get_form(
request, obj, **kwargs
)
if obj is None:
model_form._meta.fields = self._add_fields
model_form.base_fields = OrderedDict(**[
(field, model_form.base_fields[field])
for field in self._add_fields
])
return model_form
I had same problem.
I wanted to exclude some columns only in create form.
You can override get_exclude() method
from django.contrib import admin
from . import models
#admin.register(models.Toys)
class ToysAdmin(admin.ModelAdmin):
def get_exclude(self, request, obj=None):
exclude = list(super().get_exclude(request, obj) or [])
if obj==None:
# columns to exclude when it's create form
exclude += ["status","column_to_hide_on_create"]
return tuple(set(exclude))
I have a UserProfile model that I want to let people update if they wish to.
views.py
from django.views.generic.edit import CreateView, UpdateView
class UserProfileUpdateView(UpdateView):
model = UserProfile
form_class = UserProfileForm
template_name = "preferences.html"
success_url = "/profile/"
def get_context_data(self, *args, **kwargs):
context = super(UserProfileUpdateView, self).get_context_data(*args, **kwargs)
return context
urls.py
from profiles import views
from profiles.views import UserProfileCreateView, UserProfileUpdateView
urlpatterns = [url(r'^profile/(?P<pk>\d+)$/edit', UserProfileUpdateView.as_view(), name='update_profile'),]
Now my problem is that when I try to go to http://127.0.0.1:8000/profile/someusername/edit I get a 404. I don't understand exactly what's happening with the pk in the urlpatterns. What url pattern should I use to see the page where I can update the profile? Or is there something wrong here?
I
You have an $ in the middle of your regex, which makes no sense because that is the terminator character. Remove it.
I´m having a really hard time with this. I have extended the Django user model. I created a separate app call "userprofile" (i have 2 apps: 'userprofile' and 'Administration') with new models.py:
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
profile_image = models.ImageField(upload_to="/perfil/", blank=True, null=True)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user = u)[0])
the urls.py:
urlpatterns = patterns('',
url(r'^perfil/$', 'apps.userprofile.views.user_profile', name= 'perfil'),
)
and a views.py:
# Create your views here.
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
from django.core.context_processors import csrf
from forms import UserProfileForm
from django.contrib.auth.decorators import login_required
#login_required
def user_profile(request):
if request.method == 'POST':
form = UserProfileForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return HttpResponseRedirect('../index')
else:
user = request.user
profile = user.profile
form = UserProfileForm(instance = profile)
args = {}
args.update(csrf(request))
args['form'] = form
return render_to_response('profile.html', args)
and of course a forms.py:
from django import forms
from models import UserProfile
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Layout
from crispy_forms.bootstrap import (FormActions, )
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
helper = FormHelper()
helper.form_method = 'POST'
helper.layout = Layout(
'profile_image',
FormActions(Submit('Editar', 'Editar', css_class= 'btn-primary'))
)
def save(self, commit=True):
fact = super(UserProfileForm, self).save(commit=False)
if commit:
fact.save()
return fact
So, what i´m trying to do is to let the user upload an image an let it use it as a profile image. I set the:
AUTH_PROFILE_MODULE = 'apps.userprofile.UserProfile' (the app is inside a folder call 'apps' that´s why the first 'apps' before userprofile)
in the settings.py, and i added the urls of 'userprofile' to the main project. Now I have the template where i can upload an image, the problem is that the image is never saved in the database so I can´t call a function to display the image in a template, let´s say the User Profile page.
Does anyone looking at the code knows what I am doing wrong?
According to the Django 1.7 docs, ModelForm classes must explicitly specify which fields should be updated when the save() method is called. Try adding fields = __all__ to your UserProfileForm class meta.
Relevant section of ModelForm documentation
Notice the first line of the Note in that link:
Any fields not included in a form by the above logic will not be set
by the form’s save() method.
I am still learning Django myself but that's what I would try first. :)