I'm able to insert (lame) static text onto the change form admin page, but I'd really like it to use the context of the current object being edited!
For instance, I want to format on the MyObject change form a URL to include the ID from a ForeignKey connected object (obj) as a link.
My admin objects:
class MyObjectChangeForm(forms.ModelForm):
class Meta:
model = MyObject
fields = ('field1', 'obj',)
class MyObjectAdmin(admin.ModelAdmin):
form = MyObjectChangeForm
list_display = ('field1', 'obj')
def render_change_form(self, request, context, *args, **kwargs):
self.change_form_template = 'admin/my_change_form.html'
extra = {'lame_static_text': "something static",}
context.update(extra)
return super(MyObjectAdmin, self).render_change_form(request,
context, *args, **kwargs)
My template templates/admin/my_change_form.html:
{% extends "admin/change_form.html" %}
{% block form_top %}
{{ lame_static_text }}
<a href="http://example.com/abc/{{ adminform.data.obj.id }}?"/>View Website</a>
{% endblock %}
The {{adminform.data.obj.id}} call obviously doesn't work, but I'd like something along those lines.
How do I insert dynamic context from the current object into the admin change form?
Add your extra context in change_view
class MyObjectAdmin(admin.ModelAdmin):
# A template for a very customized change view:
change_form_template = 'admin/my_change_form.html'
def get_dynamic_info(self):
# ...
pass
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['osm_data'] = self.get_dynamic_info()
return super(MyObjectAdmin, self).change_view(
request, object_id, form_url, extra_context=extra_context,
)
I believe the magic variable you seek is 'original', this contains the python object the change form is editing:
<a href="http://example.com/abc/{{ original.id }}?"/>View Website</a>
Related
I am very new to django Class based views, trying to integrate it with my existing project.
My goal is to use same Class based view to Create and Update student application form.
I am trying to integrate CreateUpdateView from the #scubabuddha's answer from the solution.
views.py
from createupdateview import CreateUpdateView
class StudentView(CreateUpdateView):
template_name="admission.html"
Model = Student
form_class = StudentForm
def get(self, request, *args, **kwargs):
return self.post(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
forms = {"userform": UserForm(request.POST or None), guardian..., contact..., # other 5-6 forms}
if request.POST:
invalid_forms = [form.is_valid() for form in self.forms.values()]
if not all(invalid_forms):
messages.error(request,'You must fill up all the valid mandatory form fields!')
return render(request, 'admission.html', forms)
#-- Logic to validate and save each form
...
return render(request, 'admission.html', forms)
return render(request, 'admission.html', forms)
This is perfectly working for CreateView, but not able to understand how to use this for UpdateView or editview. If user hits editview, {{form|crispy}} should also display the details to allow user to edit the form.
urls.py (I also want to merge below 2 urls to 1, can we do this?)
from django.urls import path
from students import views
from students.views import StudentList, StudentView
urlpatterns = [
path('', StudentList.as_view(), name='students'),
path('add/', StudentView.as_view(), name='addview'),
path('<int:pk>/edit/', StudentView.as_view(), name='editview'),
...
]
I want to display, all the student details in the UpdateView forms -
admission.html
<form class="form" name="admissionForm" id="admissionForm" method="post"
enctype="multipart/form-data" action="{% url 'addview' %}">
{% csrf_token %}
<div class="pages">
<h4 class="mt-2 mb-3">Personal Information</h4>
{% include "student_errors.html" %}
{{userform|crispy}} #-- It should display student details
{{guardian_form|crispy}}
{{contact_form|crispy}}
....
<div class="btn_container">
<button type="submit" class="btn btn-info float-right btn-next">Submit</button>
</div>
</div>
P.S. I kept minimal code here, in actual production its huge code. (Migrating Django applcation 1.9 to 3.0)
The CreateUpdateView you are using from this solution inherits from ModelFormMixin and expect to handle only one form (initialization, form_class, saving, ...). And in your code your are rewriting get() and post() method so it make no sense to inherit from CreateUpdateView.
Here is how you can do it using a simple View (untested) :
from django.http import Http404
from django.views.generic import View
from django.shortcuts import render
class StudentView(View):
template_name = "admission.html"
def get_object(self):
if self.kwargs.get('pk'):
try:
obj = Student.objects.get(pk=pk)
except Student.DoesNotExist:
raise Http404("No student found matching the query")
else:
return obj
return None # create view
def get_view(self, request, *args, **kwargs):
forms = {"userform": UserForm(request.POST or None, instance=self.object), guardian..., contact...} # other 5-6 forms}
if request.POST:
invalid_forms = [form.is_valid() for form in self.forms.values()]
if not all(invalid_forms):
messages.error(request,'You must fill up all the valid mandatory form fields!')
else:
#-- Logic to validate and save each form
return render(request, self.template_name, forms)
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return self.get_view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return self.get_view(request, *args, **kwargs)
This question have been answered before, e.g here: Proper way to handle multiple forms on one page in Django
So before it gets marked as a duplicate. I'll try to explain why its different.
I've got three tables, Project, ProjectUser and User. ProjectUser is a join table to indicate what users belongs to what project.
I'm trying to create a view that lets users update project details (e.g. name of project), and also add users to the project (which is indicated by a dropdown that shows all available users like the standard one for models with foreign keys in the django admin panel). All works fine until I'm trying to pass an id from the views to the formclass and submit.
views.py
class ProjectUpdateView(UpdateView):
form_class = ProjectUpdateForm
second_form_class = ProjectUserAddForm
template_name = 'projects/project_edit.html'
success_url = reverse_lazy('projects:list')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
id_ = self.kwargs.get("id")
project = Project.objects.get(id=id_)
if 'form' not in context:
context['form'] = self.form_class()
if 'form2' not in context:
team = Organization.objects.get(id=project.organization_id)
context['form2'] = self.second_form_class(queryset=team) # <-- here is where I wish to pass a queryset, which fails when trying to submit form2.
context['project_users'] = ProjectUser.objects.filter(project__id=project.id).select_related("project")
context['team'] = Organization.objects.get(id=project.organization_id)
return context
def get_object(self):
id_ = self.kwargs.get("id")
return get_object_or_404(Project, id=id_)
def form_invalid(self, **kwargs):
return self.render_to_response(self.get_context_data(**kwargs))
def form_valid(self, form):
project_id = self.kwargs.get("id")
if self.request.POST.get("form2") == 'Add':
ProjectUser.objects.create(user_id=self.request.POST.get("user"), project_id=project_id)
form.save()
success_url = reverse("projects:edit", args=(project_id,))
return HttpResponseRedirect(success_url)
def post(self, request, *args, **kwargs):
# get the user instance
self.object = self.get_object()
# determine which form is being submitted
# uses the name of the form's submit button
if 'form' in request.POST:
# get the primary form
form_class = self.get_form_class()
form_name = 'form'
else:
# get the secondary form
form_class = self.second_form_class
form_name = 'form2'
# get the form
form = self.get_form(form_class)
# validate
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(**{form_name: form})
projects_edit.html
<form action="{% url 'projects:edit' project.id %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form.name|as_crispy_field}}
<input name="form" value="Update" type="submit"></input>
</form>
<form action="{% url 'projects:edit' project.id %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form2.user}}
<input name="form2" value="Add" type="submit"></input>
</form>
forms.py
class ProjectUpdateForm(ModelForm):
class Meta:
model = Project
fields = ["name"]
class ProjectUserAddForm(ModelForm):
def __init__(self, queryset, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['user'].queryset = User.objects.filter(organizations_organizationuser__organization__id=queryset.id) # here is where I wish to pass the id of the queryset from the form class
class Meta:
model = ProjectUser
fields = ["user"]
Rendering the forms works just fine with the desired queryset, but when I try to submit the second form (adding a user to the ProjectUserForm, I just get a
__init__() missing 1 required positional argument: 'queryset' error.
Any ideas on how to solve this? Perhaps I'm making it way more complicated than it should
I have also added a screenshot if it helps: https://imgur.com/a/uqu0UeB
I am using django admin and grappelli and wanted to override the default view for a certain model.
I overrode the grapelli change_form template:
```
{% extends "grappelli:admin/change_form.html" %}
{% block javascripts %}
{{ block.super }}
<script type="text/javascript">{{Object}}</script>
{% endblock %}
```
I have 2 questions:
How can I access the model that is passed by default by django? Neither Object nor opts seem to work.
How do I override the default to view (what's the method I need to override) to add new variables into template context?
Thanks in advance.
you can override with change_view in admin.py and pass variables through extra_context. e.g:
class GivenModel(models.Model):
def change_view(self, request, object_id, form_url='', extra_context=None):
your_obj = GivenModel.objects.get(id=asset_id.group(1))
extra_context = {'title': your_obj.description}
return super(GivenModel, self).change_view(request, object_id, form_url, extra_context)
I have a Django (1.8) model that has some class based generic views: list, update, delete, detail, create.
https://docs.djangoproject.com/en/1.8/ref/class-based-views/
On the detail or list view, I have a button that I want to do this:
Create a copy of the object
Load the data into a form and display for the user to edit/save the new object (could use the existing update or create views, or a new one?)
I can clone the model with this info:
How do I clone a Django model instance object and save it to the database?
But I can't make the leap to get this done by starting at a view and ending at a form with the copied object data.
Thanks!
partial views.py
class List(ListView):
model = Announcement
template_name = 'announcements/list.html'
class Create(CreateView):
model = Announcement
form_class = AnnouncementForm
template_name = 'announcements/form.html'
def form_valid(self, form):
data = form.save(commit=False)
data.author = self.request.user
data.save()
return super(Create, self).form_valid(form)
class Update(UpdateView):
model = Announcement
form_class = AnnouncementForm
template_name = 'announcements/form_update.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(Update, self).dispatch(*args, **kwargs)
partial forms.py
class AnnouncementForm(forms.ModelForm):
class Meta:
model = Announcement
exclude = ['author']
partial list.html
{% for object in object_list %}
<p>object.title</p>
<a class="btn btn-danger" href="{% url 'announcements:delete' object.id %}" role="button">Delete</a>
<a class="btn btn-info" href="{% url 'announcements:update' object.id %}" role="button">Edit</a>
<a class="btn btn-primary" href="" role="button">Copy</a>
{% endfor %}
What I hit the "Copy" button in list.html, I want to duplicate the object and open the new duplicate in a form for editing.
It think I figured it out!
urls.py
#eg: myapp/5/copy/
#where 5 is the item I want to copy
url(r'^(?P<id>[0-9]+)/copy/$', views.item_copy, name='item_copy'),
views.py:
def item_copy(request, id):
new_item = get_object_or_404(MyModel, pk = id)
new_item.pk = None #autogen a new pk (item_id)
new_item.name = "Copy of " + new_item.name #need to change uniques
form = MyForm(request.POST or None, instance = new_item)
if form.is_valid():
form.save()
return redirect('my_view')
context = {
"form": form,
#other context
}
return render(request, "form.html", context)
class CopyView(ManageAnnouncement, DeleteView):
def dispatch(self, *args, **kwargs):
obj = self.get_object()
obj.pk = None
copy = obj.save()
return HttpResponseRedirect('/announcement/edit/%s' %(copy.id))
# Change the redirect page to the one you need.
I have inherited a base class called ManageAnnouncement. You can put methods or variables common to multiple classes in an abstract base class and inherit it in adding, editing deleting, copying etc, so that code gets 'dry'.
I'll be implementing a customized class based generic view by sub-classing ListView in my views.py. My question is how will be able to access the request (HttpRequest object) parameter in my sub-class? The HttpRequest object that I am pertaining to is the default request parameter for all functions inside views.py. Example:
def search(request):
To be clearer, here's what I've tried so far:
**views.py
class CustomListView(ListView):
temp = ""
def get(self, request, *args, **kwargs):
self.temp = request.GET.get('temp')
return super(CustomListView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(CustomListView, self).get_context_data(**kwargs)
context['temp'] = self.temp
return context
**urls.py
url(r'^temp/$, CustomListView.as_view(queryset=Document.objects.all()[:1],template_name="temp.html")),
**temp.html
{% extends 'base.html' %}
{% block content %}
<h2>{{ temp }}
{% endblock %}
But all I am seeing when I run the server and access /temp/ (temp.html) is 'None'. So meaning, 'temp' is "" or 'temp' was not created at all.
Any ideas are greatly appreciated. Thanks!
In general, you can use self.request in CBV methods that haven't been passed a request.
So you can use
context['temp'] = self.request.GET.get('temp')
in your get_context_data method, and then delete your get override method entirely.