Given the following situation:
# models.py
class Book(Model):
pass
# views.py
class BookDetail(DetailView):
model = Book
# books/urls.py
urlpatterns += [path('detail/<int:pk>', BookDetail.as_view(), 'book_detail')]
# page/urls.py
urlpatterns += [path('books/', include('books.urls'))]
I can load the detail view for the object with the private key id 42 at /books/detail/42/. If I am in another request with a completely different path and hold a reference to the object with the private key id 42, is there an "official" or builtin way to generate the url /books/detail/42/? Preferably outside of templating, so I can respond with a JSON.
Or is the idiomatic way to parametrize the path elements (books and detail) and just rebuild it myself?
Yes, you can make use of get_absolute_url for model-specific views and the {% url ... %} template tag to calculate the name of a view.
Model-specific views
If a Model has a specific view to show details, you can implement a get_absolute_url [Django-doc] on your model, like:
from django.urls import reverse
class Book(Model):
def get_absolute_url(self):
return reverse('book_detail', kwargs={'pk': self.pk})
Here we use reverse [Django-doc] to "calculate" the URL for the given name of the view (specified in your path(..., name='book_detail')) with as pk parameter, the pk of self.
In your template you can then write:
{{ my_object }}
with my_object the name of the variable in your template.
It is note that the redirect [Django-doc] function understands the get_absolute_url, and thus you can write return redirect(my_object) in a view, and it will automatically call get_absolute_url to redirect to the proper view.
If you serialize a model with the Django REST framework, then you can reuse the get_absolute_url as well, by using a URLField [drf-doc] for example:
from rest_framework.serializers import ModelSerializer, URLField
class BookSerializer(serializers.ModelSerializer):
absolute_url = serializers.URLField(
read_only=True,
source='get_absolute_url'
)
This specific use case is documented in the documentation of the Django REST framework.
Making use of {% url ...%} in the template
You can also calculate the URL of a view, by using the {% url ... %} template tag [Django-doc]. You can for example write:
{{ my_object }}
to calculate the URL, just like we did with the get_absolute_url. It is however useful as well for other, non model-specific views.
You should use Django Rest Framework
All built-in, you just have to set up.
Related
Say I have a url likes this
path(
'foo/<int:foo_id>/edit/',
views.FooView.as_view(),
name='foo',
),
and a view likes this:
def get(self, request, foo_id):
I find a common idiom is getting the URL variable foo_id into the context.
The only thing context has access to be default is request. I tried checking request and request.GET and could not see anything.
Is there a better way than:
Manually adding url variables to the context_data after get_context_data()
or passing it into get_context_data from a custom call from get? (ugly because class based views expect the same get_context_data signature)
The url parameters are stored in the .kwargs of the view. You thus can access and render these with:
{{ view.kwargs.foo_id }}
There is a reference with the name view that is passed which is the View object that is constructed when handling a request. We thus access the .kwargs attribute of that View object, and in the kwargs, we look for kwargs['foo_id'].
A peculiarity in Django is that a TemplateView [Django-doc] passes all it kwargs items as context data, if your view is thus a TemplateView, then you can render this with
<!-- only a TemplateView -->
{{ foo_id }}
I did a custom loginView and I can't reach the extra_context dictionary in my template. (authentification works fine)
my view file:
from django.contrib.auth import login
from .models import EsportUser
class LoginViewCustom(LoginView):
#esport_user = EsportUser.objects.first()
#extra_context = {'test42': esport_user}
template_name = 'users/login_register.html'
extra_context = {'test42': 'test'}
my template file (login_register.html):
{% trans "Account" %} {{ test42 }}
my urls file:
path('login/', views.LoginViewCustom.as_view(), name='login', ),
Thanks, Stéphane
It's not too late to share a good answer:
Check out this similar question , basically you can pass your additional extract context the as_view() method when calling it in the url.
So in your url.py file, you can have something like this :
path('login/', views.LoginViewCustom.as_view(extra_context={'test42': 'test'}), name='login', )
Bird up #Stéphane, welcome to StackOverflow.
I think your problem is that EsportUser is your model (a model being the template you use to create objects).
Once you create an object instance of EsportUser model, (i.e. create a user, by the looks of what you're doing), you want to query your database to retrieve objects that belong to that model, then pass that object (or some of the object attributes) to the template.
Assuming you have an object instance of EsportUser (i.e. created a user), you could try:
# here you query the first EsportUser object
esport_user = EsportUser.objects.first()
# here you pass that object into extra_context - not the model itself
extra_context = {'test42': esport_user}
in my URL for the createview, I want there to be a '?', from where I can pass an argument to the nect page. I am using class based views. For example:
www.site.com/appname/appointment/add/?Name=1
And my HTML would be:
href={% url 'People:appointment-create' Patient.id %}
Currently my URL is like so:
re_path(r'appointment/add/$', views.appointmentCreate.as_view(), name='appointment-create'),
and my view is:
class appointmentCreate(LoginRequiredMixin, CreateView):
model = appointment
form_class = AppointmentForm
def get_initial(self):
patient = self.request.GET.get('patient')
return {
'Patient': patient,
}
How would i go about doing this?
You can try something like this:
href={% url 'People:appointment-create' %}?patient_id={{ Patient.id }}
(so just pass the query arguments after the url as normally).
Also, if you want a more general solution on this problem (automatically generate the form's initial values from query parameters) take a look at this section https://spapas.github.io/#configure-the-form-s-initial-values-from-get-parameters from by article on CBVs.
So here's the basics:
first of all I would like to let you know about below line you just wrote:
{% url 'People:appointment-create' Patient.id %}
to match above url you will need to include below url:
path(r'appointment/add/<int:patient>', views.appointmentCreate.as_view(), name='appointment-create'),
This is the difference between request parameters and kwargs here you pass patient id as kwargs and access in Class base view in self.kwargs
The constructed url will be like below:
www.site.com/appname/appointment/add/1/
Next thing is that if you want to post request parameter (which don't require to add any additional url in your urls.py) you can use as below:
href="{% url 'People:appointment-create' %}?patient={{Patient.id}}"
Note that the parameter you use here will be available in view if you use name in request parameter then you need to access it as self.request.GET.get('name') and if you want to use patient as request parameter then you can access it as self.request.GET.get('patient').
I'd like to use the django admin to produce a read-only view of an object which contains an "Edit" button which switches you to the usual change view of the same object.
I know how to use the readonly attributes to produces a read-only view, but I don't know how to produce two views, one read-only and one that allows changes.
I'd like to reuse as much of the admin interface for this as possible, rather than writing a view from scratch.
Note that this question isn't about permissions: all users will have permission to change the objects. It's just that I would prefer that they not use the change_view unless they do intend to make changes, reducing the risk of accidental changes or simultaneous changes.
Here's an answer that literally does what I asked with only a few lines of code and just a couple of template changes:
class MyModelAdmin(admin.ModelAdmin):
fieldsets = [...]
def get_readonly_fields(self, request, obj=None):
if 'edit' not in request.GET:
return <list all fields here>
else:
return self.readonly_fields
Now the usual URL for the change_form will produce a read only change_form, but if you append "?edit=1" to the URL, you will be able to edit.
The change_form template can also be customized depending on whether "?edit=1" is in the URL. To do this, put 'django.core.context_processors.request' in TEMPLATE_CONTEXT_PROCESSORS in settings.py, and then use request.GET.edit in the template.
For example, to add an "Edit" button when not in edit mode, insert
{% if not request.GET.edit %}
<li>Edit</li>
{% endif %}
just after <ul class="object-tools"> in change_form.html.
As another example, changing change_form.html to contain
{% if save_on_top and request.GET.edit %}{% submit_row %}{% endif %}
will mean that the submit row will only be shown in edit mode. One can also hide the Delete buttons on inlines, etc, using this method.
For reference, here is what I put in settings.py:
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.contrib.messages.context_processors.messages',
# Above here are the defaults.
'django.core.context_processors.request',
)
I'd suggest to reconsider using custom views. With the help of generic DetailView, you'll need to write literally two lines of code. The template won't require much work either. You just extend standard change_form.html template, overriding field_sets block.
I know how to use the readonly attributes to produces a read-only view, but I don't know how to produce two views, one read-only and one that allows changes.
You actually can register one model in the admin twice[1], using proxy models. (There're some inconsistencies with permissions for proxy models, but it may not be a problem in your case.)
It seems to be possible to register multiple admin sites[2], too.
I'd like to reuse as much of the admin interface for this as possible, rather than writing a view from scratch.
Interface reuse as such has little to do with views, being mostly template- and style-related thing. View, however, should provide the template context necessary for interface reuse, as you correctly pointed out.
If you decide to go with multiple views per one ModelAdmin, then it might be useful for you to check how django-reversion project implements its admin integration: reversion/admin.py.
References
[1] Multiple ModelAdmins/views for same model in Django admin
[2] Registering Multiple Admin Sites
You will need to change template django admin uses for model form. Make it readonly and add a button to original template linked to another url.
Note:
I highly discourage this approach, you will definitely not prevent simultaneous changes. This should be solved with locking.
Also, I recommend using django-reversion for keeping history of objects and eliminating "accidental changes" risk.
You could create a custom view and display your object there.
To create a custom view in an admin module, override the get_urls() method :
class MyAdmin(admin.ModelAdmin):
…
def get_urls(self):
urls = super(MyAdmin, self).get_urls()
my_urls = patterns('',
url(r'^custom_view/(?P<my_id>\d+)/$', self.admin_site.admin_view(self.custom_viem), name='custom_view')
)
return my_urls + urls
def custom_view(self, request, my_id):
"""Define your view function as usual in views.py
Link to this view using reverse('admin:custom_view')
"""
from myapp import views
return views.custom_view(request, my_id, self)
In views.py :
def custom_view(request, object_id, model_admin):
admin_site = model_admin.admin_site
opts = model_admin.model._meta
my_object = get_object_or_404(MyObject, pk=object_id)
# do stuff
context = {
'admin_site': admin_site.name,
'opts': opts,
'title': _('My custom view'),
'root_path': '%s' % admin_site.root_path,
'app_label': opts.app_label,
'my_object': my_object,
}
return render_to_response('my_template.html', context,
context_instance=RequestContext(request))
In your template, use {% extends "admin/base_site.html" %} to keep the admin look and feel.
The below code is implementation of read-only admin using proxy models.
Models.py
//real model
class CompetitionEntry(models.Model):
pass
//Proxy model
class ReviewEntry(CompetitionEntry):
class Meta:
proxy = True
def save(self, *args, **kwargs):
pass
admin.py
//editable admin
class CompetitionEntryAdmin(admin.ModelAdmin):
pass
admin.site.register(CompetitionEntry, CompetitionEntryAdmin)
// read-only admin (assign only "change" permission for this)
class ReviewEntryAdmin(admin.ModelAdmin):
pass
admin.site.register(ReviewEntry, ReviewEntryAdmin)
Before Django 1.0 there was an easy way to get the admin url of an object, and I had written a small filter that I'd use like this: <a href="{{ object|admin_url }}" .... > ... </a>
Basically I was using the url reverse function with the view name being 'django.contrib.admin.views.main.change_stage'
reverse( 'django.contrib.admin.views.main.change_stage', args=[app_label, model_name, object_id] )
to get the url.
As you might have guessed, I'm trying to update to the latest version of Django, and this is one of the obstacles I came across, that method for getting the admin url doesn't work anymore.
How can I do this in django 1.0? (or 1.1 for that matter, as I'm trying to update to the latest version in the svn).
You can use the URL resolver directly in a template, there's no need to write your own filter. E.g.
{% url 'admin:index' %}
{% url 'admin:polls_choice_add' %}
{% url 'admin:polls_choice_change' choice.id %}
{% url 'admin:polls_choice_changelist' %}
Ref: Documentation
from django.core.urlresolvers import reverse
def url_to_edit_object(obj):
url = reverse('admin:%s_%s_change' % (obj._meta.app_label, obj._meta.model_name), args=[obj.id] )
return u'Edit %s' % (url, obj.__unicode__())
This is similar to hansen_j's solution except that it uses url namespaces, admin: being the admin's default application namespace.
I had a similar issue where I would try to call reverse('admin_index') and was constantly getting django.core.urlresolvers.NoReverseMatch errors.
Turns out I had the old format admin urls in my urls.py file.
I had this in my urlpatterns:
(r'^admin/(.*)', admin.site.root),
which gets the admin screens working but is the deprecated way of doing it. I needed to change it to this:
(r'^admin/', include(admin.site.urls) ),
Once I did that, all the goodness that was promised in the Reversing Admin URLs docs started working.
Essentially the same as Mike Ramirez's answer, but simpler and closer in stylistics to django standard get_absolute_url method:
from django.urls import reverse
def get_admin_url(self):
return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name),
args=[self.id])
Using template tag admin_urlname:
There's another way for the later versions (>=1.10), recommend by the Django documentation, using the template tag admin_urlname:
{% load admin_urls %}
Add user
Delete this user
Where opts is something like mymodelinstance._meta or MyModelClass._meta
One gotcha is you can't access underscore attributes directly in Django templates (like {{ myinstance._meta }}) so you have to pass the opts object in from the view as template context.
For pre 1.1 django it is simple (for default admin site instance):
reverse('admin_%s_%s_change' % (app_label, model_name), args=(object_id,))
I solved this by changing the expression to:
reverse( 'django-admin', args=["%s/%s/%s/" % (app_label, model_name, object_id)] )
This requires/assumes that the root url conf has a name for the "admin" url handler, mainly that name is "django-admin",
i.e. in the root url conf:
url(r'^admin/(.*)', admin.site.root, name='django-admin'),
It seems to be working, but I'm not sure of its cleanness.
If you are using 1.0, try making a custom templatetag that looks like this:
def adminpageurl(object, link=None):
if link is None:
link = object
return "%s" % (
instance._meta.app_label,
instance._meta.module_name,
instance.id,
link,
)
then just use {% adminpageurl my_object %} in your template (don't forget to load the templatetag first)
For going to the admin page or admin login page we can use the below link. It works for me -
{% url 'admin:index' %}
This url takes me directly to the admin page.
Here's another option, using models:
Create a base model (or just add the admin_link method to a particular model)
class CommonModel(models.Model):
def admin_link(self):
if self.pk:
return mark_safe(u'<a target="_blank" href="../../../%s/%s/%s/">%s</a>' % (self._meta.app_label,
self._meta.object_name.lower(), self.pk, self))
else:
return mark_safe(u'')
class Meta:
abstract = True
Inherit from that base model
class User(CommonModel):
username = models.CharField(max_length=765)
password = models.CharField(max_length=192)
Use it in a template
{{ user.admin_link }}
Or view
user.admin_link()