I am trying to set GET parameters on a LinkColumn based on Accessors with django-tables2.
Let's say:
urls.py
urlpatterns = [
...
url(r'^rqGET$', views.rqGET, name='rqGET'),
...
]
views.py
def rqGET(request):
#... do something with request.GET
tables.py
class MyTable(tables.Table):
id = LinkColumn('rqGet',text='Link') # do something with Accessors to make a GET string, maybe ?id=A('pk')
class Meta:
model = MyModel #(has field 'id')
I want to use reverse to get the correct url, then construct the GET parameter string. For example /rqGET?id=1 ('1' would be different in each row).
That's not really how Accessors work in django-tables2. Django-tables2 uses django's reverse to generate urls. If you want reverse to be able to generate the url, you need to define the parameters in your urls, which will be passed as arguments to your view function:
# urls.py
urlpatterns = [
...
url(r'^rqGET/(?P<id>\d+)$', views.rqGET, name='rqGET'),
...
]
# views.py
def rqGET(request, id):
# do something with request/id.
If you don't want to change the way your url is formatted, you can use a custom render_ function on your MyTable like this:
# tables.py
from django.core.urlresolvers import reverse
class MyTable(tables.Table):
id = LinkColumn('rqGet', text='Link') # do something with Accessors to make a GET string, maybe ?id=A('pk')
class Meta:
model = MyModel
def render_id(self, record):
url = reverse('rqGET')
return format_html('{}', url, record.id, 'Link')
This will render a link with format /rqGET/?id=<id>.
Related
I've got a class-based-view and I'm trying to implement breadcrumbs into it.
I'm using django-bootstrap-breadcrumbs & django-view-breadcrumbs, but because of our unique URL structure we have an ID in the URL of almost all of the urls.
I've setup the breadcrumbs appropriately, but need to get the person_id kwarg into the 'crumbs' attribute on the ListView
URLs look like this:
path(
"<person_id>/trees/",
views.TreeListView.as_view(),
name="tree_list",
),
path(
"<person_id>/trees/<pk>/",
views.TreeDetailView.as_view(),
name="tree_view",
),
path(
"<person_id>/trees/<tree_id>/planes/",
views.PlaneListView.as_view(),
name="plane_list",
),
path(
"<person_id>/trees/<tree_id>/cord/<pk>/",
views.CordDetailView.as_view(),
name="cord_view",
),
I've setup my view according to the documentation:
class TreeListView(
LoginRequiredMixin,
UserPassesTestMixin,
ListBreadcrumbMixin,
ListView
):
# pylint: disable=too-many-ancestors
login_url = "/login/"
model = Tree
template_name = "tree_list"
crumbs = [('My Test Breadcrumb', reverse('tree_list', args=[self.kwargs["person_id"]]))]
As you can see in the last line - the crumbs is what is supposed to create the breadcrumbs that display on the page.
The issue is that I get an error (which seem pretty obvious) that there is no 'self' item with that object.
My question is - how do I get that person_id from the URL so I can pass it as the argument to the URL?
You can define crumbs as property:
class TreeListView(
LoginRequiredMixin,
UserPassesTestMixin,
ListBreadcrumbMixin,
ListView
):
# pylint: disable=too-many-ancestors
login_url = "/login/"
model = Tree
template_name = "tree_list"
#property
def crumbs(self):
return [('My Test Breadcrumb', reverse('tree_list', args=[self.kwargs["person_id"]]))]
I have urls reporting?id=1 and reporting?students=bachelor. For both these urls I am using same base url 'reporting' in urls.py.
urls.py
from .views import Reporting
urlpatterns = [
path('', AnalyticsData.as_view()),
path('reporting', Reporting.as_view()),
path('index', index.as_view())
]
How could I use the same class view to execute different get methods so that for the first URL, a method to get students by id is executed and for the second URL, a method to get bachelor students is executed. For now, I am writing code in the following way
views.py
class Reporting(APIView):
def get(self, request):
id = self.request.GET.get("id")
student_type = self.request.GET.get("students")
if id:
*logic*
if student_type=="bachelor":
*logic*
But I don't know if this is the right way. Also, if there are multiple parameters than there will be many if conditions. Is there any other way available?
I would have a variable in my class based view that tells what to do when it is called. Something like this. This var variable gets the value of whatever that is passed when calling it from the urls.py so you can use that to your advantage.
class Reporting(APIView):
var = "" # HERE
def get(self, request):
id = self.request.GET.get("id")
student_type = self.request.GET.get("students")
if id:
*logic*
if student_type=="bachelor":
*logic*
Then in my urls.py I would call the class with that parameter and handle the logic accordingly.
from .views import Reporting
urlpatterns = [
path('reporting', Reporting.as_view(var="yourvalue")), #HERE
]
Then handle logic according to the var variable.
So now if you want your class to behave differently for another case, then you can change var variable accordingly and the behaviour would also change. You just need to change the value in your url.
urlpatterns = [
path('reporting', Reporting.as_view(var="yourvalue")), #HERE
path('new_reporting', Reporting.as_view(var="your_new_value"))
]
So for your case you can pass this var along to your views and then you can use if else to process the get function. Don't forget the self btw.
if self.var == "id":
# Logic for id
elif self.var == "students":
# Logic for students
I wrote this code that dynamically generates url patterns from the database. These urls have only one level path: domain.com/something.
someapp/models.py
class SomeModel(models.Model):
pattern = models.CharField(max_length=50)
name = models.CharField(max_length=50)
text = models.CharField(max_length=50)
someapp/apps.py
class SomeAppConfig(AppConfig):
name = 'someapp'
def ready(self):
from .models import SomeModel
from .urls import urlpatterns
from . import views
urls_in_db = SomeModel.objects.all()
for url_in_db in urls_in_db:
urlpatterns.append(path(url_in_db.pattern,
views.SpecialView.as_view(),
name=url_in_db.name)
someapp/views.py
class SpecialView(generic.TemplateView):
template_name = 'template/view_template.html'
model = SomeModel
def get_context_data(self, **kwargs):
context = super(SpecialView, self).get_context_data(**kwargs)
context['content'] = SomeModel.objects.get(pattern=self.request.path)
return context
Is this solution an anti-pattern? And, if so, why?
Thanks
Yes, your solution is an anti-pattern. Django supports parameters in URL patterns that get captured and become available in the corresponding view. Using these URL parameters, you can write and maintain a single URL pattern for every record of a certain type in your database.
Take a look at this example of URL parameters.
Finally, also note how your solution could potentially have very poor performance since you are potentially creating millions of URL patterns depending on the size of your database.
TL;DR
Is there a way to have (namespaced,) well-named views defined when using ModelAdmin.get_urls and ModelAdmins extended by inheritance?
Preferably without resorting to ModelAdmin.model._meta or some other solution of slightly questionable nature.
Pretext
View names added through get_urls get overridden when using and inheriting from custom ModelAdmins.
That is, the view name admin:tighten gets overriden in the following example:
class Screw(models.Model):
"A screw"
class HexCapScrew(Screw):
"A hex cap screw"
class ScrewAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super(ScrewAdmin, self).get_urls()
extra_urls = patterns('',
url(r'^tighten/$', self.tighten, name='tighten'),
)
return extra_urls + urls
def tighten(self, request):
pass
class HexCapScrewAdmin(ScrewAdmin):
pass
admin.site.register(Screw, ScrewAdmin)
admin.site.register(HexCapScrew, HexCapScrewAdmin)
On shell the following happens:
In [1]: reverse('admin:tighten')
Out[1]: u'/admin/parts/hexscrew/tighten/'
This is of course understandable since the registration of HexCapScrewAdmin overides the tighten in ScrewAdmin however now it's impossible to reverse ScrewAdmin.tighten.
A preferred solution
However I would like to be able to
reference both views separatedly and
preferably have views in their own instance namespaces.
Progress so far
The best I've come up with is the following setup (can be copy&pasted directly to some app for testing):
from django.contrib import admin
from django.db import models
class Screw(models.Model):
"A screw"
class Meta:
app_label = 'parts'
class HexCapScrew(Screw):
"A hex cap screw"
class Meta:
app_label = 'parts'
proxy = True
class ScrewAdmin(admin.ModelAdmin):
def tighten(self, request):
pass
def get_urls(self):
urls = super(ScrewAdmin, self).get_urls()
extra_urls = patterns('',
url(r'^tighten/$', self.tighten, name='tighten'),
)
# Find out the slugified name of the model this admin is bound to
# TODO: Feels dirty
model_name = self.model._meta.model_name
# Add the to `extra_urls` to their own namespace
namespaced_extra_urls = patterns('',
url(r'^', include(extra_urls, namespace=model_name, app_name='screw')),
)
return namespaced_extra_urls + urls
class HexCapScrewAdmin(ScrewAdmin):
pass
admin.site.register(Screw, ScrewAdmin)
admin.site.register(HexCapScrew, HexCapScrewAdmin)
Now I have the following:
In [1]: reverse('admin:screw:tighten')
Out[1]: u'/admin/parts/screw/tighten/'
In [2]: reverse('admin:hexscrew:tighten')
Out[2]: u'/admin/parts/hexscrew/tighten/'
In [3]: reverse('admin:screw:tighten', current_app='hexscrew')
Out[3]: u'/admin/parts/hexscrew/tighten/'
which is nice and works but includes a bit of hackery.
Is this the best that's available or am I just missing something? Any suggestions?
(At least one other way would be to do as Django's ModelAdmin.get_urls use ModelAdmin.model._meta to parametrize the view names but then I would use the namespaces.)
If you look at the way the admin does it here, you will see that in addition to defining the url, the model admin also prefixes the app_label and model_name to the url name, thus avoiding the subclassing issue to begin with. It also has the advantage of securing the view against unauthorised users (using the self.admin_site.admin_view decorator). Your get_urls() method would then become:
def get_urls(self):
from django.conf.urls import url
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.model_name
urlpatterns = super(ScrewAdmin, self).get_urls()
urlpatterns.append(
url(r'^tighten/$', wrap(self.tighten), name='%s_%s_tighten' % info))
return urlpatterns
Then, you'd look up your url like: reverse('admin:app_screw_tighten') or reverse('admin:app_hex_screw_tighten').
I'm pretty new at Django and wondering what is the difference between defining model vs queryset in a generic view like ListView. Here's my code example in my urls.py file for the project:
urlpatterns = patterns('',
url(r'^$', ListView.as_view(
model=Person,
context_object_name='people',
template_name='index.html',
)),
)
I've also used the same this:
urlpatterns = patterns('',
url(r'^$', ListView.as_view(
queryset=Person.objects.all,
context_object_name='people',
template_name='index.html',
)),
)
And received the same result on my view. I'm assuming there are different things you can do with a queryset?
Using model=Person or queryset=Person.objects.all give the same result.
Let's look at the code. A ListView has the following method:
def get_queryset(self):
"""
Get the list of items for this view. This must be an interable, and may
be a queryset (in which qs-specific behavior will be enabled).
"""
if self.queryset is not None:
queryset = self.queryset
if hasattr(queryset, '_clone'):
queryset = queryset._clone()
elif self.model is not None:
queryset = self.model._default_manager.all()
else:
raise ImproperlyConfigured(u"'%s' must define 'queryset' or 'model'"
% self.__class__.__name__)
return queryset
As you can see, it first looks for self.queryset and, if that does not exist, for self.model. So there are two possibilities to specify a list: you can provide a queryset yourself or you can specify a model class (in which case Django will call the all() method of the default manager, which is objects).
I'm assuming there are different things you can do with a queryset?
Yes. If you specify a model, then you get all instances by default. But if you specify a queryset, you can also call other methods of a model manager, such as Person.objects.children() which could return only persons with age <= 12.