I've got a trouble: I can easily update my html template sending the "object_list" object and iterating it with
{%for item in object_list%}
#... some operation
{% endfor %}
However I don't know yet to do the same with my overriden admin-template in "template/admin/add_form.html": how to send and retrieve a method I added to my admin.py ModelAdmin custom class?
here there's my url.py
from django.urls import path
from django.conf.urls import url
from . import views as mysearch_views
from django.contrib import admin
from django.views.generic import ListView, DetailView
from .models import MySearch
# Admin custom
admin.sites.AdminSite.site_header = ‘to be decided’
admin.sites.AdminSite.site_title = ‘to be decided’
admin.sites.AdminSite.index_title = ‘to be decided’
urlpatterns = [
url(r'^$', mysearch_views.my_search, name="my_search"),
url(r'^(?P<id>\d+)/(?P<slug>[\w-]+)/$', DetailView.as_view(model=MySearch, template_name="queries.html"), name="queries"),
url(r'^contacts', mysearch_views.contacts, name="contacts"),
url(r'^result-table', mysearch_views.create_table, name="results"),]
Thanks in advance
In your ModelAdmin class, you can override the add_view method to add custom context data for the Add Form. There are other methods for other admin views: change_view for the Change Form, etc.
def add_view(self, request, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['your_custom_data'] = self.your_custom_method()
return super().add_view(request, form_url, extra_context=extra_context)
You can then use {{ your_custom_data }} in your overridden admin template.
add_view and the other related methods are documented here.
Related
sorry for all the code given but i have tried so hard for a day still ending up with a problem when passing stage_id as a parameter in the form action url
I have a class Rapports that holds a file (i prefer pdf files) and have a 1to1 relationship with an other class from an other application, i'm sending the stage.id from a html page in Stages app and it's working the id is passed and i can pass it to an other html page but when i write the code i need and passe the same url pattern in the action attribute of the template i want to show(rapport/test.html down below) it return NoReverseMatch and i can't figure out why. Is it because i'm trying to upload a file or is it something else? (1st time working with files)
{% block rpt %}
<li class="nav-item">
<a class="nav-link" href="{% url 'rapport:depo' stages.id %}">
déposer le rapport
</a>
</li>
{% endblock rpt %}
rapport.Rapports
from django.db import models
from satges.models import Stages
class Rapports(models.Model):
stage=models.OneToOneField(
Stages,
on_delete=models.CASCADE,
primary_key=True,
)
src=models.FileField(
("rapport"),
upload_to='rapports/',
max_length=100
)
r_soummit=models.DateTimeField(
auto_now=False,
auto_now_add=False,
)
satges.Stages
class Stages(models.Model):
#Stages attrs that are saved so i think no need to show them here
def est_ete(self):
# comment: this fn returns true if it's a summer intership
if(self.deb.month in[6,7,8]):
return True
# end def
def get_absolute_url(self):
return reverse("satges:det_stg", kwargs={"pk": self.pk})
#saving Stages to database with CreateView is working
def __str__(self):
return self.etd + ' ' + self.nature + " - " + self.au
rapport.forms.py
from django import forms
from .models import Rapports
class RapportsForm(forms.ModelForm):
class Meta:
model = Rapports
fields = ['src']
widgets = {
'src': forms.FileInput(),
}
rapport.views
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from django.views.generic import CreateView
from satges.models import Stages
from .models import Rapports
from .forms import RapportsForm
# Create your views here.
def afficher(request, stage_id):
# comment:
#return HttpResponse('stage: %s'% stage_id)
if request.method == 'POST':
form = RapportsForm(request.POST, request.FILES)
else:
form=RapportsForm()
return render(request, 'rapport/test.html', {'form': form})
class RapportCreateView(CreateView):
model = Rapports
form_class=RapportsForm
template_name = "rapport/test.html"
# end def
'''
def afficher_depo(request, stage_id):
form=RapportsForm()
print(stage_id)
return render(request,'rapport/depo.html', {'form': form})
def depo(request, stage_id):
if request.method == 'POST':
form = RapportsForm(request.POST)
stage=get_object_or_404(Stages, pk=stage_id)
src = form.cleaned_data['src']
rpt=Rapports(stage=stage,src=src)
rpt.save()
else:
return HttpResponse('rapport isn\'t saved')
return render(request,'rapport/depo.html', {'form':form})
'''
the project url conf
from django.contrib import admin
from django.urls import include, path
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('satges.urls')),
path('journal/', include('journaux.urls')),
path('rapport/<int:pk>', include('rapport.urls')),
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
rapport.urls.py
from django.urls import path, re_path
from . import views
app_name = 'rapport'
urlpatterns = [
path('',views.RapportCreateView.as_view(), name='depo'),
]
test.html (this file is just for testing the main file is called depo.html and it extends otthe page and have the same behavior when i remove the url in action the form appears)
<html>
<head>
<title>test</title>
</head>
<body>
<form method="post" enctype="multipart/form-data" action="{% url 'rapport:depo' satge_id %}">
<!--when i remove the url tag the input form appears and i can choose a file but i ofc can't send it without the action-->
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="déposer votre rapport" name="depo">
</form>
</body>
</html>
this should be all the code related to the problem, i have tried googling, youtube, django doc and with the changes i made along the day the errors differed but the one i couldn't get through is NoReverseMatch
error
Error during template rendering
In template /home/ssoya/Documents/pfe/new/pages/templates/rapport/test.html, error at line 6
Reverse for 'depo' with arguments '('',)' not found. 1 pattern(s) tried: ['rapport/(?P<pk>[0-9]+)\\Z']
after the 1st answer my view became like this but no matter i change it still no use
class RapportsCreateView(CreateView):
model = Rapports
form_class=RapportsForm
template_name = "rapport/depo.html"
def get_context_data(self, **kwargs):
context=super(RapportsCreateView, self).get_context_data(**kwargs)
#stage=Stages.objects.get(pk=self.kwargs.get('pk'))
context['stages'] = Stages.objects.filter(pk=self.kwargs.get('pk'))
return context
then i tried something else: I replaced stage.id with 1 then 13 to see what happens. It raised IntegrityError
NOT NULL constraint failed: rapport_rapports.stage_id
The above exception (NOT NULL constraint failed: rapport_rapports.stage_id) was the direct cause of the following exception:
response = get_response(request)
i'm willing to restructure the question once i found a soloution or understand it more
it's because of stage_id as it's not defined so url will not be found as it needs the parameter stage_id, check the view that rendering your template it is not retuning the stage_id variable
add this to your create view
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# context is a dict so
#update it with your stage id
return context
in this function add the stage_id so it get passed to the template
i can say it is 90% Solved
the problem i had in this question was related to the context in my view and after the answer of #mohamed and some googling and tries i made the view ended up working and saving the file to the server and the database and putting the id of the Satges object as the id of rapport as the attribute satge_id then redirects the user to an other page
here are the modifications i made
views.py
class RapportsCreateView(CreateView):
model = Rapports
form_class = RapportsForm
template_name = "rapport/depo.html"
def form_valid(self, form):
self.stage_id = self.kwargs['pk']
stage = Stages.objects.get(id=self.stage_id)
form.instance.stage_id = stage.id
form.instance.r_soummit = timezone.now()
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
stage = Stages.objects.get(pk=self.kwargs.get('pk'))
context['stage'] = stage
context['stage_id'] = stage.id
return context
models.Rapports
def get_absolute_url(self):
return reverse("rapport:det_rpt", kwargs={"pk": self.pk})
def __str__(self):
return self.src
It's my first time to use ListView and it doesn't work and give me error.
I put get_query but they still give me same error. How can I fix the problem?
And everytime when I write code in views.py I always used 'def' not 'class'. But could see many people use (and also django documents) 'class' for ListView. So for general render stuffs we use 'def' and for django.views.generic stuffs we use class right? Why they distinguished these two?
This is error what I got.
ImproperlyConfigured at /search/results
ListView is missing a QuerySet. Define ListView.model, ListView.queryset, or override ListView.get_queryset().
urls.py
from django.urls import path
from django.conf import settings
from django.views.generic import ListView, TemplateView
from . import views
app_name = 'search'
urlpatterns = [
path('', TemplateView.as_view(template_name = "search/index.html")),
path('results', ListView.as_view(template_name = 'search/results.html')),
path('beerlist', views.beerlist, name='beerlist'),
path('<int:beerinfo_id>', views.beerinfo, name='beerinfo'),
]
views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.db.models import Q
from django.views.generic import ListView, TemplateView
from .models import Beerinfo
# Create your views here.
def index(TemplateView):
template_name = 'search/index.html'
def results(ListView):
model = Beerinfo
template_name = 'search/results.html'
def get_queryset(self):
query = self.request.GET.get('q')
object_list = Beerinfo.objects.filter(
Q(name__icontains = query) | Q(label__icontains = query)
)
return obejct_list
index.html
<form action="{% url 'search:results' %}" name="se">
<label for='search'>What do you want to find?</label>
<input type="text" name='q'>
<input type="submit">
</form>
results.html
<ul>
{% for beer in ojbect_list %}
<li>{{ beer.name }}</li>
{% endfor %}
</ul>
models.py
from django.db import models
# Create your models here.
class Beerinfo(models.Model):
name = models.CharField(max_length=100)
label = models.CharField(max_length=500)
def __str__(self):
return self.name
You need to define the class that the list view will work with. For example:
class UserListView(ListView):
model = User
You can use a function (def) to accomplish the same thing that a generic view class, the difference is that most of what you write in the function is already defined in the class. In my example above, that class already handles the rendering of a default template, a context with the list of object of that template and pagination. The idea is to keep your code DRY.
The second advantage is that it creates a standard for your code, for example the default template to be used is
%(app_label)s/%(model_name)s%(template_name_suffix)s.html, so if your app name is users and your model is User, the this view expects a template named users/userlist.html
To use the pagiation simply set the paginate_by attribute of the class.
If you are trying to implement a simple view (for example all CRUD actions, then is very likely that you will benefit from using clases. Another good thing that classes give you, is that you can inherit goodies, for example, you can create a BaseListView class that inherits from ListView and set paginate_by to 25. If all your clases inherit from BaseListView then all your list will be paginated by 25 elements.
In views.py change def to class , you need to define a class to use Listview, Class Results(ListView). In urls.py, you are calling Listview , you should call views.Results.as_view()
This is a view written for my posts app in Django. The problem is that after filling the update form and submitting it happens successfully. But it creates confusion for the user because the same HTML page is there and how can I redirect into the updated object?
def post_update(request,id=None):
instance=get_object_or_404(Post,id=id)
if instance.created_user != request.user.username :
messages.success(request, "Post owned by another user, You are having read permission only")
return render(request,"my_blog/denied.html",{})
else :
form=PostForm(request.POST or None,request.FILES or None,instance=instance)
if form.is_valid():
instance=form.save(commit=False)
instance.save()
context={ "form":form,
"instance":instance }
return render(request,"my_blog/post_create.html",context)
As already suggested by #mdegis you can use the Django redirect function to redirect to another view or url.
from django.shortcuts import redirect
def view_to_redirect_to(request):
#This could be the view that handles the display of created objects"
....
perform action here
return render(request, template, context)
def my_view(request):
....
perform form action here
return redirect(view_to_redirect_to)
Read more about redirect here and here
You can pass positional or keyword argument(s) to the redirect shortcut using the reverse() method and the named url of the view you're redirecting to.
In urls.py
from news import views
url(r'^archive/$', views.archive, name='url_to_redirect_to')
In views.py
from django.urls import reverse
def my_view(request):
....
return redirect(reverse('url_to_redirect_to', kwargs={'args_1':value}))
More about reverse Here
You can use redirect from http shortcuts.
from django.shortcuts import redirect
def my_view(request):
...
object = MyModel.objects.get(...)
return redirect(object) #or return redirect('/some/url/')
Here is the link to official docs.
To redirect from a view to another view, you need to give the conbination of the app name "myapp", colon ":" and the view name "dest_view" which is set in the path in "myapp/urls.py" as shown below. And, you don't need to modify the path in "myapp/urls.py" if you pass data with session with request.session['key'] as shown below:
# "myapp/views.py"
from django.shortcuts import render, redirect
def redirect_view(request):
# Here
request.session['person'] = {'name': 'John', 'age': 27}
# Here
return redirect("myapp:dest_view")
def destination_view(request):
return render(request, 'myapp/index.html', {})
You need to give the view name "dest_view" to path() in "myapp/urls.py" as shown below:
# "myapp/urls.py"
from django.urls import path
from . import views
app_name = "myapp"
urlpatterns = [ # This is view name
path('dest/', views.destination_view, name="dest_view")
]
Then, this is Django Template:
# "myapp/index.html"
{{ request.session.person.name }} {# John #}
{{ request.session.person.age }} {# 27 #}
from django.urls import reverse
def my_view(request):
....
return redirect(reverse('url_to_redirect_to', kwargs={'args_1':value(object.id for specific id)}))
I want to create custom page for admin panel without model. For first i copy index.html to project folder:
mysite/
templates/
admin/
index.html
Then add to apps block my code:
<div class="module">
<table summary="{% blocktrans with name="preferences" %}Models available in the preferences application.{% endblocktrans %}">
<caption>{% blocktrans with name="preferences" %}Preferences{% endblocktrans %}</caption>
<tr>
<th scope="row">Preferences</th>
<td>{% trans 'Change' %}</td>
</tr>
</table>
</div>
This works good, then I create new page /templates/admin/preferences/preferences.html and
add to urls.py:
url(r'^admin/preferences/$', TemplateView.as_view(template_name='admin/preferences/preferences.html')),
And add code to preferences.html:
{% extends "admin/base_site.html" %}
{% block title %}Test page{% endblock %}
Run it and see message with error "The requested admin page does not exist.". What I do wrong?
You need to add your admin URL before the URL patterns of the admin itself:
urlpatterns = patterns('',
url(r'^admin/preferences/$', TemplateView.as_view(template_name='admin/preferences/preferences.html')),
url(r'^admin/', include('django.contrib.admin.urls')),
)
This way the URL won't be processed by Django's admin.
Years go by and still a relevant answer to this can be posted.
Using Django 1.10+ you can do:
security/admin.py (this is your app's admin file)
from django.contrib import admin
from django.conf.urls import url
from django.template.response import TemplateResponse
from security.models import Security
#admin.register(Security)
class SecurityAdmin(admin.ModelAdmin):
def get_urls(self):
# get the default urls
urls = super(SecurityAdmin, self).get_urls()
# define security urls
security_urls = [
url(r'^configuration/$', self.admin_site.admin_view(self.security_configuration))
# Add here more urls if you want following same logic
]
# Make sure here you place your added urls first than the admin default urls
return security_urls + urls
# Your view definition fn
def security_configuration(self, request):
context = dict(
self.admin_site.each_context(request), # Include common variables for rendering the admin template.
something="test",
)
return TemplateResponse(request, "configuration.html", context)
security/templates/configuration.html
{% extends "admin/base_site.html" %}
{% block content %}
...
{% endblock %}
See Official ModelAdmin.get_urls description (make sure you select proper Django version, this code is valid for 1.10 above)
Note the use of get_urls() above.
This new admin page will be
accessible under:
https://localhost:8000/admin/security/configuration/
This page will be protected under admin login area
You should be using admin's get_urls.
If you want to create a custom page just to place there an arbitrary form to handle user input, you may give django-etc a try. There's etc.admin.CustomModelPage you can use:
# admin.py
from etc.admin import CustomModelPage
class MyPage(CustomModelPage):
title = 'My custom page' # set page title
# Define some fields you want to proccess data from.
my_field = models.CharField('some title', max_length=10)
def save(self):
# Here implement data handling.
super().save()
# Register the page within Django admin.
MyPage.register()
Here's an example of everything that should be needed (as of Django 1.6) for a custom admin page that is linked to from a button next to the "History" button in the top right of an object's detail page:
https://gist.github.com/mattlong/4b64212e096766e058b7
Full example:
from django.urls import path
from django.contrib import admin
from django.db import models
class DummyModel(models.Model):
class Meta:
verbose_name = 'Link to my shiny custom view'
app_label = 'users' # or another app to put your custom view
#admin.register(DummyModel)
class DummyModelAdmin(admin.ModelAdmin):
def get_urls(self):
view_name = '{}_{}_changelist'.format(
DummyModel._meta.app_label, DummyModel._meta.model_name)
return [
path('my_view/', MyCustomView.as_view(), name=view_name)
]
With this approach Django's makemigrations command will create DB migration to create table for DummyModel.
Extending the AdminSite class worked best for me, as per django's documentation. This solution protects the page(s) under the admin site login mechanism, and setting it up is easier than it may look:
Where you have other admin code (eg. myapp/admin.py), extend the default class:
from django.contrib.admin import AdminSite
class CustomAdminSite(AdminSite):
def get_urls(self):
custom_urls = [
path('admin/preferences/', self.admin_view(views.my_view)),
]
admin_urls = super().get_urls()
return custom_urls + admin_urls # custom urls must be at the beginning
site = CustomAdminSite()
# you can register your models on this site object as usual, if needed
site.register(Model, ModelAdmin)
Implement the view
def my_view(request):
return render(request, 'admin/preferences/preferences.html')
Use that admin site in urls.py, instead of the default one
from myapp import admin
# now use admin.site as you would use the default django one
urlpatterns = [
# ...
path('admin/', admin.site.urls),
# ...
]
If you want to hook a page into the existing admin site, then you can do the following, which is based on #arnaud-p's answer above. Arnaud's answer didn't work for me, as subclassing adminsite's get_url function lost access to existing admin pages until I added the registry as follows.
Using the following method, your additional pages will require staff access, and you don't need to change your urls.py, so this is great for making admin pages for apps etc... You can pass each_context in the view in order to get permissions etc.
works for django 3.2.9
In admin.py
from django.contrib import admin
from django.urls import path
from . import views
class CustomAdminSite(admin.AdminSite):
def get_urls(self):
self._registry = admin.site._registry
admin_urls = super().get_urls()
custom_urls = [
path('preferences/', views.Preferences.as_view(admin=self), name="preferences"),
]
return custom_urls + admin_urls # custom urls must be at the beginning
def get(self):
request.current_app == self.name
return super().get(request)
def get_app_list(self, request):
app_list = super().get_app_list(request)
app_list += [
{
"name": "My Custom Preferences App",
"app_label": "Preferences",
# "app_url": "/admin/test_view",
"models": [
{
"name": "Preferences",
"object_name": "preferences",
"admin_url": "/admin/preferences",
"view_only": True,
}
],
}
]
return app_list
site = CustomAdminSite()
the view...
class Preferences(views.generic.ListView):
admin = {}
def get(self, request):
ctx = self.admin.each_context(request)
return render(request, 'admin/preferences/preferences.html', ctx)
the template...
{% extends "admin/base_site.html" %}
{% block content %}
...HELLO WORLD!
{% endblock %}
I am new to django (1.2.4). I have created some crud with generic views. But How can I show something like "The student was added successfully" when student is created using django's messaging framework?
As of Django 1.6+, using any class-based generic views, you can rely on the successMessageMixin. It's as simple as adding the mixin to your class definition and setting success_message attribute to whatever you want.
As Olivier Verdier mentioned, please remember to display messages in your main template!
a simple example from the docs:
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreate(SuccessMessageMixin, CreateView):
model = Author
success_url = '/success/'
success_message = "%(name)s was created successfully"
a more complex example:
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import CreateView
from myapp.models import ComplicatedModel
class ComplicatedCreate(SuccessMessageMixin, CreateView):
model = ComplicatedModel
success_url = '/success/'
success_message = "%(calculated_field)s was created successfully"
def get_success_message(self, cleaned_data):
# cleaned_data is the cleaned data from the form which is used for string formatting
return self.success_message % dict(cleaned_data,
calculated_field=self.object.calculated_field)
As far as I know, there isn't a straightforward way of doing this using traditional generic views. I've always felt that the documentation on generic views was pretty lacking and so never used them.
In theory you could use a decorator by making the assumption that a redirect meant a successful submission.
So you could write something like this (none of this code has been tested):
urls.py:
try:
from functools import wraps
except ImportError:
from django.utils.functional import wraps
from django.http import HttpRedirectResponse
from django.contrib import messages
from django.views.generic import *
def add_message(success_message=None):
def decorator(func):
def inner(request, *args, **kwargs):
resp = func(request, *args, **kwargs)
if isinstance(resp, HttpRedirectResponse):
messages.success(request, message)
return resp
return wraps(func)(inner)
return decorator
student_info_edit = {
'template_name': 'myapp/student/form.html',
'template_object_name': 'student',
'form_class': studentForm,
}
student_info_new = {
'template_name': 'myapp/student/form.html',
'form_class': studentForm,
'post_save_redirect': '/myapp/students/',
}
urlpatterns += patterns('',
url(r'^students/$', list_detail.object_list, { 'queryset': Student.objects.all() }, name="students"),
url(r'^students/(?P<object_id>\d+)/$', add_message("Student record updated successfully")(create_update.update_object), student_info_edit, name="student_detail"),
url(r'^students/new$', add_message("The student was added successfully.")(create_update.create_object), student_info_new, name="student_new"),
)
All that said and coded, Django 1.3 introduced class-based generic views, so if you're interested in moving onto Django 1.3 you should look into those. They may allow more customization, not sure.
In the long run I rarely see the benefit form using generic views, and this goes double for things like add/update.
The functionality that you are asking for is already implemented in Django generic views:
https://github.com/django/django/blob/1.2.X/django/views/generic/create_update.py#L115
You will see the messages by displaying messages in your main template.
Actually I think the documents explain it pretty well for generic/function based views:
https://docs.djangoproject.com/en/2.0/ref/contrib/messages/
It basically passes context to your template with an if statement to display that context or not.
View:
from django.contrib import messages
def home_page(request):
if request.method == 'POST':
messages.success(request, 'Student added successfully')
context = {}
return render(request, 'homepage/index.html', context)
else:
form =yourForm()
return render(request, 'homepage/index.html', form)
And then it will be displayed in your template using the following. Remember to iterate '...because otherwise the message storage will not be cleared for the next request':
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
As it renders the page again just add an anchor tag to your form and include in your form action i.e.
action="{% url 'home_page:index' %}#subscribe"
If you're using bootstrap add class alert-success