So currently I'm on a page with a url link like this
urls.py :
path('key/<int:pk>', views.KeyDetailView.as_view(), name='roomkey-detail'),
views.py :
class KeyDetailView(generic.DetailView):
model = RoomKey
this lists out a list of keys available to be borrowed for a particular room. Then when I try to head to the next page, where is a request I can make to borrow out one of the keys for that room, Here is the urls and views responsible for rendering the roomkey-request page
urls.py :
path('key/<int:pk>/request', views.KeyRequestCreate.as_view(), name='roomkey-request')
views.py :
class KeyRequestCreate(CreateView):
model = KeyRequest
fields = ['roomkey', 'requester', 'borrower', 'request_comments']
template_name = 'catalog/roomkey_request_form.html'
there is a button that on that page that links to a terms and agreement page that looks like this
roomkey_request_form.html :
terms and conditions
urls.py :
path('key/<int:pk>/request/agreement', views.KeyAgreement, name='key-agreement'),
views.py :
def KeyAgreement(request):
return render(
request,
'catalog/roomkey_agreement.html',
)
however when try to click on that request button to request a key, django throws an error
NoReverseMatch at /catalog/key/2/request
Reverse for 'key-agreement' with arguments '('',)' not found. 1 pattern(s)
tried: ['catalog\\/key\\/(?P<pk>[0-9]+)\\/request\\/agreement$']
I have a button on the terms and agreement to go back to the request page something that looks like this
<button href="{% url 'roomkey-request' roomkey.pk %}" >Return to request</button>
will this return to the request page with the correct pk? I think i'm just confused with how the url handles pk and how it get's passed on.I am thinking this had to do with something the keyagreement not being able to take in that pk from the details page, can someone please explain to me what I am doing wrong or point me to some resource that can help me understand how urls pass along the pk from view to view? I am fairly new to django so thanks for your help in advance!!
Try:
def KeyAgreement(request, pk): #-->pk in argument
return render(
request,
'catalog/roomkey_agreement.html',
)
If you want to use roomkey.pk in the roomkey_request_form.html template, you need to add roomkey to the template context. You can do this in the get_context_data method.
Since you already have the roomkey pk from the URL, you can remove it from fields. Then set roomkey in the form_valid method before saving.
class KeyRequestCreate(CreateView):
model = KeyRequest
fields = ['requester', 'borrower', 'request_comments']
template_name = 'catalog/roomkey_request_form.html'
def get_context_data(self, **kwargs):
context = super(KeyRequestCreate, self).get_context_data(**kwargs)
context['roomkey'] = get_object_or_404(RoomKey, pk=pk)
def form_valid(self, form):
form.instance.roomkey = get_object_or_404(RoomKey, pk=pk)
return super(KeyRequestCreate, self).get_form(form)
If you want to use roomkey in the agreement view, you'll have to make some changes to it as well.
First, you need to add pk to the function signature since you have <int:pk> in its URL pattern.
Then, you need to include roomkey in the template context.
from django.shortcuts import get_object_or_404
def key_agreement(request, pk):
roomkey = get_object_or_404(roomkey, pk=pk)
return render(
request,
'catalog/roomkey_agreement.html',
{'roomkey': roomkey}
)
Note that I've renamed the view function to key_agreement to match the recommended style. You'll need to update the URL pattern as well.
path('key/<int:pk>/request/agreement', views.KeyAgreement, name='key-agreement'),
I'm trying to get all attributes of a single object. I keep getting a "Devices matching query does not exist." I just cannot figure out my issue.
Models.py
`class Devices(models.Model):
category_id = models.ForeignKey(Category, on_delete=models.CASCADE)
device_description = models.CharField(max_length=100)
device_status = models.CharField(max_length=50)
device_date = models.DateTimeField()
device_user = models.CharField(max_length=50)`
Views.py
def view_status(request, pk=None):
device = Devices.objects.get(pk=pk)
return render(request, 'homesite/device_status.html', device)
urls.py
url(r'^viewstatus/$', views.view_status, name='ViewStatus'),
here is the url I use to call http://localhost:8000/homesite/viewstatus/?pk=1
device_satus.html
{% extends "base.html" %}
{% block head %}
<title>Device Status</title>
{% endblock%}
{% block body %}
<h3>Device Status Detail</h3>
{{ devices.device_description }}
{{ devices.device_status }}
{{devices.device_date|date:"Y-m-d H:m:s"}}
{% endblock %}
There are 4 records in my able so I know there is a match for PK=1.
Note, that this is not the usual way to build an URL for accessing a specific object. Below I present first the approach that integrates pk in the URI and second the one passing pk as a parameter.
1. Approach
Here you put the pk in the URI and request something like http://localhost:8000/homesite/viewstatus/1/. If you do so, you need to adapt your urls.py by specifying what part of the URI is the pk you want:
# urls.py
url(r'^viewstatus/(?P<pk>\d+)/$', views.view_status, name='ViewStatus'),
The way you wrote the view is fine:
def view_status(request, pk=None):
if pk is not None:
device = Devices.objects.get(pk=pk)
else:
device = None
return render(request, 'homesite/device_status.html', {'device' : device})
Now, views.view_status will be called with both the request object and the pk as arguments and objects.get will behave as you expected, if the pk you put in the URI exists in you database.
Note that this is the preferred way to get an object.
2. Approach
In this case you pass the pk as a parameter, so call http://localhost:8000/homesite/viewstatus/?pk=1, for example. Now pk is a parameter of a GET request. In this case:
# urls.py
url(r'^viewstatus/$', views.view_status, name='ViewStatus'),
And the view only takes the request object as argument. Within the view you can get the pk as follows:
def view_status(request):
pk = request.GET.get('pk', None)
if pk is not None:
device = Devices.objects.get(pk=int(pk))
else:
device = None
return render(request, 'homesite/device_status.html', {'device' : device})
So in this case your view does not take any arguments other than the request object.
Another issue is in your view function: Django's shortcut render takes a dict object for the optional argument context. Currently you directly pass a Devices object. You need to update your return statement in view_status:
return render(request, 'homesite/device_status.html', {'device' : device})
Hope that helps!
I get an error 'Devices' object is not iterable
urls.py
this is how the url is set up.
url(r'^viewstatus/$', views.view_status, name='ViewStatus'),
but is should be like this
url(r'^viewstatus/(?P<pk>\d+)/$', views.view_status, name='ViewStatus'),
so that I can call like this correct? http://localhost:8000/homesite/viewstatus/1/
views.py
def view_status(request):
pk = request.GET['pk']
device = Devices.objects.get(pk=pk)
return render(request, 'homesite/device_status.html', device
so i need the corresponding views.py code to work with
http://localhost:8000/homesite/viewstatus/1/
I've stared at this for hours so I know I'm missing something simple.
Try changing your view function:
def view_status(request):
pk = request.GET['pk']
device = Devices.objects.get(pk=pk)
return render(request, 'homesite/device_status.html', device)
Let me know if it helps :)
I have a little question regarding Forms / Views which don't use a Model object. I seem to have it set up almost the way it should, but I can't seem to figure out how to pass data around to initialise the fields in my edit form.
What I have to do is get data from a REST server which was developed using Delphi. So this django thingie won't be using the normal django ORM model thing. Currently I have it working so my app displays a list of departmets which it got using a REST call to the server. Each department has it's ID as a hyperlink.
My next step / thing I would like to do is display a form in which the user can edit some values for the selected department. Logically everything seems to be hooked up together the way it should (as far as I can see). Sadly ... for whatever reason ... I can't seem to pass along information about the clicked ID or even the selected object in my list to the detail view.
Would anyone be able to help me out ? This is what I have so far :
The urls.py :
# DelphiClient/urls.py
from django.conf.urls import patterns
from django.conf.urls import url
from . import views
urlpatterns = patterns("",
url(
regex=r"^Departments$",
view=views.DelphiDepartmentsListView.as_view(),
name="Departments"
),
url(
regex=r'^Department/(?P<pk>\d+)/$',
view=views.DepartmentFormView.as_view(),
name='department_update'
),
)
The views.py :
# DelphiClient/views.py
...
from .client import DelphiClient
from .forms import DepartmentForm
class DelphiDepartmentsListView(TemplateView):
template_name = 'DelphiDepartmentList.html'
def get_context_data(self, **kwargs):
client = DelphiClient()
departments = client.get_department()
context = super(DelphiDepartmentsListView, self).get_context_data(**kwargs)
context['departments'] = departments
#client.update_department(1, 'Update From Django')
return context
class DepartmentFormView(FormView):
template_name = 'DepartmentUpdate.html'
form_class = DepartmentForm
success_url = '/DelphiClient/Departments'
def get_initial(self, **kwargs):
"""
Returns the initial data to use for forms on this view.
"""
initial = super(DepartmentFormView, self).get_initial(**kwargs)
# How can I get the ID passed along from the list view
# so I can get the correct object from my REST server and
# pass it along in the Initial ???
return initial
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
print "form.data {0}".format(form.data)
client = DelphiClient()
client.update_department(form.data["flddepartmentId"],form.data["flddepartmenet"])
return super(DepartmentFormView, self).form_valid(form)
The forms.py :
# DelphiClient/forms.py
from django import forms
from .client import DelphiClient
class DepartmentForm(forms.Form):
# How can I fill in the values for these fields using an object passed in
# thhrough Initial or the context?
flddepartmentId = forms.IntegerField(label="Department ID") #, value=1)
flddepartmenet = forms.CharField(label="New Description", max_length=100)
def update_department(self, *args, **kwargs):
#print "update_department"
#print self.data
#print self.data["flddepartmenet"]
client = DelphiClient()
client.update_department(self.data["flddepartmentId"],self.data["flddepartmenet"])
And the template for the form :
<h1>Update Department</h1>
<p>Update Department? {{ department.flddepartmentid }}</p>
<p>Something : {{ something }}</p>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<p><label for="id_flddepartmentId">Department ID:</label> <input id="id_flddepartmentId" name="flddepartmentId" type="number" value="1"></p>
<p><label for="id_flddepartmenet">New Description:</label> <input id="id_flddepartmenet" maxlength="100" name="flddepartmenet" type="text"></p>
<input type="submit" value="OK">
</form>
As you can see ... I'm close ... but no cigar yet :-) Since I'm completely new to Python / Django and have been learning on the go, I have no idea what I'm doing wrong or where I should look.
If anyone would be able to help or point me in the right direction it would be really appreciated.
The positional and name-based arguments are stored in self.args and self.kwargs respectively (see the docs on name based filtering). Therefore you can access the pk with self.kwargs['pk'].
I'm not sure that you should include flddepartmentId as an editable field in the form. It means that users could go to /Department/1/, but then enter flddepartmentId=2 when they submit the form. It might be better to remove the field from the form, then use the value from the URL when calling update_department.
client.update_department(self.kwargs['pk'],self.data["flddepartmenet"])
If you are sure that you want to include flddepartmentId in your form, then your get_initial method should look as follows:
def get_initial(self, **kwargs):
"""
Returns the initial data to use for forms on this view.
"""
initial = super(DepartmentFormView, self).get_initial(**kwargs)
initial['flddepartmentId'] = self.kwargs['pk']
return initial
I have seen similar questions and answers but none that address my problem.
I want my view to perform a User Group check and then pass that via variable to the template. The template will then use that to appear differently to different user groups.
My views.py:
def cans(request):
is_canner = request.user.groups.filter(name='canner') #check if user group = canner
can_list = Can.objects.order_by('name')
context = {'can_list': can_list}
return render(request, 'cans/cans.html', context) #need to return is_canner variable here
And in my template I would use the variable like so:
{% if is_canner %} canner stuff goes here {% endif %}
I'm unsure how to pass this variable, I thought it used context to send it like so:
return render(request, 'cans/cans.html', context({"is_canner": is_canner}))
But this gives me errors - context is not callable.
context is not a function, its an argument to the render function, e.g.
context = {"is_canner": is_canner}
return render(request, 'cans/cans.html', context)
docs: https://docs.djangoproject.com/en/dev/topics/http/shortcuts/#render
more background info: Django - what is the difference between render(), render_to_response() and direct_to_template()?
To deal with the lack of nested inlines in django-admin, I've put special cases into two of the templates to create links between the admin change pages and inline admins of two models.
My question is: how do I create a link from the admin change page or inline admin of one model to the admin change page or inline admin of a related model cleanly, without nasty hacks in the template?
I would like a general solution that I can apply to the admin change page or inline admin of any model.
I have one model, post (not its real name) that is both an inline on the blog admin page, and also has its own admin page. The reason it can't just be inline is that it has models with foreign keys to it that only make sense when edited with it, and it only makes sense when edited with blog.
For the post admin page, I changed part of "fieldset.html" from:
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field }}
{% endif %}
to
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{% ifequal field.field.name "blog" %}
<p>{{ field.field.form.instance.blog_link|safe }}</p>
{% else %}
{{ field.field }}
{% endifequal %}
{% endif %}
to create a link to the blog admin page, where blog_link is a method on the model:
def blog_link(self):
return '%s' % (reverse("admin:myblog_blog_change",
args=(self.blog.id,)), escape(self.blog))
I couldn't find the id of the blog instance anywhere outside field.field.form.instance.
On the blog admin page, where post is inline, I modified part of "stacked.html" from:
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>
<span class="inline_label">{% if inline_admin_form.original %}
{{ inline_admin_form.original }}
{% else %}#{{ forloop.counter }}{% endif %}</span>
to
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>
<span class="inline_label">{% if inline_admin_form.original %}
{% ifequal inline_admin_formset.opts.verbose_name "post" %}
<a href="/admin/myblog/post/{{ inline_admin_form.pk_field.field.value }}/">
{{ inline_admin_form.original }}</a>
{% else %}{{ inline_admin_form.original }}{% endifequal %}
{% else %}#{{ forloop.counter }}{% endif %}</span>
to create a link to the post admin page since here I was able to find the id stored in the foreign key field.
I'm sure there is a better, more general way to do add links to admin forms without repeating myself; what is it?
Use readonly_fields:
class MyInline(admin.TabularInline):
model = MyModel
readonly_fields = ['link']
def link(self, obj):
url = reverse(...)
return mark_safe("<a href='%s'>edit</a>" % url)
# the following is necessary if 'link' method is also used in list_display
link.allow_tags = True
New in Django 1.8 : show_change_link for inline admin.
Set show_change_link to True (False by default) in your inline model, so that inline objects have a link to their change form (where they can have their own inlines).
from django.contrib import admin
class PostInline(admin.StackedInline):
model = Post
show_change_link = True
...
class BlogAdmin(admin.ModelAdmin):
inlines = [PostInline]
...
class ImageInline(admin.StackedInline):
# Assume Image model has foreign key to Post
model = Image
show_change_link = True
...
class PostAdmin(admin.ModelAdmin):
inlines = [ImageInline]
...
admin.site.register(Blog, BlogAdmin)
admin.site.register(Post, PostAdmin)
This is my current solution, based on what was suggested by Pannu (in his edit) and Mikhail.
I have a couple of top-level admin change view I need to link to a top-level admin change view of a related object, and a couple of inline admin change views I need to link to the top-level admin change view of the same object. Because of that, I want to factor out the link method rather than repeating variations of it for every admin change view.
I use a class decorator to create the link callable, and add it to readonly_fields.
def add_link_field(target_model = None, field = '', link_text = unicode):
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
def link(self, instance):
app_name = instance._meta.app_label
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
link_obj = getattr(instance, field, None) or instance
url = reverse(reverse_path, args = (link_obj.id,))
return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
link.allow_tags = True
link.short_description = reverse_name + ' link'
cls.link = link
cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + ['link']
return cls
return add_link
You can also pass a custom callable if you need to get your link text in some way than just calling unicode on the object you're linking to.
I use it like this:
# the first 'blog' is the name of the model who's change page you want to link to
# the second is the name of the field on the model you're linking from
# so here, Post.blog is a foreign key to a Blog object.
#add_link_field('blog', 'blog')
class PostAdmin(admin.ModelAdmin):
inlines = [SubPostInline, DefinitionInline]
fieldsets = ((None, {'fields': (('link', 'enabled'),)}),)
list_display = ('__unicode__', 'enabled', 'link')
# can call without arguments when you want to link to the model change page
# for the model of an inline model admin.
#add_link_field()
class PostInline(admin.StackedInline):
model = Post
fieldsets = ((None, {'fields': (('link', 'enabled'),)}),)
extra = 0
Of course none of this would be necessary if I could nest the admin change views for SubPost and Definition inside the inline admin of Post on the Blog admin change page without patching Django.
I think that agf's solution is pretty awesome -- lots of kudos to him. But I needed a couple more features:
to be able to have multiple links for one admin
to be able to link to model in different app
Solution:
def add_link_field(target_model = None, field = '', app='', field_name='link',
link_text=unicode):
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
def link(self, instance):
app_name = app or instance._meta.app_label
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
link_obj = getattr(instance, field, None) or instance
url = reverse(reverse_path, args = (link_obj.id,))
return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
link.allow_tags = True
link.short_description = reverse_name + ' link'
setattr(cls, field_name, link)
cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + \
[field_name]
return cls
return add_link
Usage:
# 'apple' is name of model to link to
# 'fruit_food' is field name in `instance`, so instance.fruit_food = Apple()
# 'link2' will be name of this field
#add_link_field('apple','fruit_food',field_name='link2')
# 'cheese' is name of model to link to
# 'milk_food' is field name in `instance`, so instance.milk_food = Cheese()
# 'milk' is the name of the app where Cheese lives
#add_link_field('cheese','milk_food', 'milk')
class FoodAdmin(admin.ModelAdmin):
list_display = ("id", "...", 'link', 'link2')
I am sorry that the example is so illogical, but I didn't want to use my data.
I agree that its hard to do template editing so, I create a custom widget to show an anchor on the admin change view page(can be used on both forms and inline forms).
So, I used the anchor widget, along with form overriding to get the link on the page.
forms.py:
class AnchorWidget(forms.Widget):
def _format_value(self,value):
if self.is_localized:
return formats.localize_input(value)
return value
def render(self, name, value, attrs=None):
if not value:
value = u''
text = unicode("")
if self.attrs.has_key('text'):
text = self.attrs.pop('text')
final_attrs = self.build_attrs(attrs,name=name)
return mark_safe(u"<a %s>%s</a>" %(flatatt(final_attrs),unicode(text)))
class PostAdminForm(forms.ModelForm):
.......
def __init__(self,*args,**kwargs):
super(PostAdminForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance',None)
if instance.blog:
href = reverse("admin:appname_Blog_change",args=(instance.blog))
self.fields["link"] = forms.CharField(label="View Blog",required=False,widget=AnchorWidget(attrs={'text':'go to blog','href':href}))
class BlogAdminForm(forms.ModelForm):
.......
link = forms..CharField(label="View Post",required=False,widget=AnchorWidget(attrs={'text':'go to post'}))
def __init__(self,*args,**kwargs):
super(BlogAdminForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance',None)
href = ""
if instance:
posts = Post.objects.filter(blog=instance.pk)
for idx,post in enumerate(posts):
href = reverse("admin:appname_Post_change",args=(post["id"]))
self.fields["link_%s" % idx] = forms..CharField(label=Post["name"],required=False,widget=AnchorWidget(attrs={'text':post["desc"],'href':href}))
now in your ModelAdmin override the form attribute and you should get the desired result. I assumed you have a OneToOne relationship between these tables, If you have one to many then the BlogAdmin side will not work.
update:
I've made some changes to dynamically add links and that also solves the OneToMany issue with the Blog to Post hope this solves the issue. :)
After Pastebin:
In Your PostAdmin I noticed blog_link, that means your trying to show the blog link on changelist_view which lists all the posts. If I'm correct then you should add a method to show the link on the page.
class PostAdmin(admin.ModelAdmin):
model = Post
inlines = [SubPostInline, DefinitionInline]
list_display = ('__unicode__', 'enabled', 'blog_on_site')
def blog_on_site(self, obj):
href = reverse("admin:appname_Blog_change",args=(obj.blog))
return mark_safe(u"<a href='%s'>%s</a>" %(href,obj.desc))
blog_on_site.allow_tags = True
blog_on_site.short_description = 'Blog'
As far as the showing post links on BlogAdmin changelist_view you can do the same as above. My earlier solution will show you the link one level lower at the change_view page where you can edit each instance.
If you want the BlogAdmin page to show the links to the post in the change_view page then you will have to include each in the fieldsets dynamically by overriding the get_form method for class BlogAdmin and adding the link's dynamically, in get_form set the self.fieldsets, but first don't use tuples to for fieldsets instead use a list.
Based on agfs and SummerBreeze's suggestions, I've improved the decorator to handle unicode better and to be able to link to backwards-foreignkey fields (ManyRelatedManager with one result). Also you can now add a short_description as a list header:
from django.core.urlresolvers import reverse
from django.core.exceptions import MultipleObjectsReturned
from django.utils.safestring import mark_safe
def add_link_field(target_model=None, field='', app='', field_name='link',
link_text=unicode, short_description=None):
"""
decorator that automatically links to a model instance in the admin;
inspired by http://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-
to-the-admin-page-o
:param target_model: modelname.lower or model
:param field: fieldname
:param app: appname
:param field_name: resulting field name
:param link_text: callback to link text function
:param short_description: list header
:return:
"""
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
def link(self, instance):
app_name = app or instance._meta.app_label
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
link_obj = getattr(instance, field, None) or instance
# manyrelatedmanager with one result?
if link_obj.__class__.__name__ == "RelatedManager":
try:
link_obj = link_obj.get()
except MultipleObjectsReturned:
return u"multiple, can't link"
except link_obj.model.DoesNotExist:
return u""
url = reverse(reverse_path, args = (link_obj.id,))
return mark_safe(u"<a href='%s'>%s</a>" % (url, link_text(link_obj)))
link.allow_tags = True
link.short_description = short_description or (reverse_name + ' link')
setattr(cls, field_name, link)
cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + \
[field_name]
return cls
return add_link
Edit: updated due to link being gone.
Looking through the source of the admin classes is enlightening: it shows that there is an object in context available to an admin view called "original".
Here is a similar situation, where I needed some info added to a change list view: Adding data to admin templates (on my blog).