I would like to have a Django detail view that shows all the models containing a foreign key for the detail view page for that foreign key value, linked from a list view.
The model ActivityID contains a list of character id's (e.g. 'F1088HW'). There are several other models, each containing the contents of a storage facility, and each item has an ActivityID. So when F1088HW is clicked on, for example, a detail page should show all the facilities that has stuff from that ActivityID.
So far I have the following view based on various other SO questions:
class ActivityDetailView(generic.DetailView):
model = ActivityID
# context_object_name = 'library_set'
# Changed to 'activity' based on comments below
# context_object_name = 'ativity'
def get_queryset(self):
pk = self.kwargs['pk']
return models.objects.filter(activityid = pk)
And ActivityID_detail.html:
{% extends "base_generic.html" %}
{% block content %}
<h1>Field Activity ID: {{ activity }} </h1>
<div style="margin-left:20px;margin-top:20px">
<h4>Libraries</h4>
<dl>
{% for library in activity.library_set.all %}
<dt>{{library}} ({{library.library_set.all.count}})</dt>
{% endfor %}
</dl>
</div>
{% endblock %}
urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('activities/', views.ActivityIDsView.as_view(), name='activities'),
path('activities/<int:pk>', views.ActivityDetailView.as_view(), name='activity-detail'),
]
But clicking on an ActivityID in the list view then returns:
AttributeError at /catalog/activities/2429
module 'django.db.models'
has no attribute 'objects'
Any idea what I'm doing wrong?
You are calling models module instead of your model.
Try:
return self.model.objects.filter(id=pk)
As Sergey has pointed out, you're calling the wrong class. But actually the whole approach makes no sense. The detail in this view is the Activity. You don't want to filter the activities by activityid, you want to get the activity by ID and then get is related libraries.
The second part of that is done by the activity.library_set.all in the template, and the first part is what the DetailView would normally do - so you don't need that get_queryset method at all. You should remove it.
Related
In my Django project, I have created a custom admin page for an app via the get_urls() method. I'd like to add a link to the app's main model index view that will take users to this custom page - however, I'm having some trouble creating this link element correctly and I don't seem to be able to piece together the right way to do it - I'm just left with a Reverse for 'export' not found. 'export' is not a valid view function or pattern name. error.
I've set up the admin for the app like so:
# my_project/observations/admin.py
from django.template.response import TemplateResponse
from django.urls import path
class ObservationAdmin(SimpleHistoryAdmin, SoftDeletionModelAdmin):
change_list_template = 'export_link.html'
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('export/', self.admin_site.admin_view(self.export_view), name='export')
]
return custom_urls + urls
def export_view(self, request):
context = dict(
self.admin_site.each_context(request),
)
return TemplateResponse(request, 'export.html', context)
and the two templates that are referenced:
# my_project/observations/templates/export.html
{% extends "admin/base_site.html" %}
{% block content %}
<div>
Some custom content
</div>
{% endblock %}
# my_project/observations/templates/export_link.html
{% extends 'admin/change_list.html' %}
{% block object-tools-items %}
<li>
Export
</li>
{{ block.super }}
{% endblock %}
Navigating directly to http://localhost:8000/admin/observations/observation/export/ works perfectly, I see the custom content page exactly as I want it... so the issue I'm striking is with the link template - I get the Reverse... error when I navigate to the model index page.
Perhaps the argument I'm passing to url is incorrect, or I need to register that URL elsewhere - but I don't quite know. The other examples of link elements like this that I've been able to find don't reference URLs created via the admin class' get_urls() method - so any guidance on this would be greatly appreciated.
Thanks very much, let me know if there's any other info that I can provide to help sort this out.
I think the problems is in missing namespace in your export_link.html template. Instead of:
Export
try:
Export
I'm setting up a webstore manager and I'm relying on django-tables2 package to show a list of products using the SimpleTableMixin. I want to add the filter/search capability to the view. As recommended by the django-tables2 package one can rely on django-filter package to provide filtering. However, it the case of a model with many fields it becomes almost impossible to query and develop forms for those efficiently. My goal is to use django-haystack to have a single input search form as a mean to query the model instances which should be displayed in a similar table/form.
I've tried to add the SimpleTableMixin to the django-haystack package generic SearchView. However I keep on getting the following error:
TypeError at /manager/front/products/
Argument data to ProductTable is required
So far my implementation goes as follow:
view:
# ############## Products ############## (previous implementation with django-filter)
# #method_decorator(staff_member_required, name='dispatch')
# class ProductList(SingleTableMixin, FilterView):
# model = Product
# table_class = tables.ProductTable
# filterset_class = filters.ProductFilter
# template_name = 'manager/front/products/product_list.html'
############## Products ##############
#method_decorator(staff_member_required, name='dispatch')
class ProductList(SingleTableMixin, SearchView):
model = Product
table_class = tables.ProductTable
template_name = 'manager/front/products/product_list.html'
tables:
import django_tables2 as tables
from front.models import Product
class ProductTable(tables.Table):
class Meta:
model = Product
template_name = 'django_tables2/bootstrap.html'
search_indexes.py:
from haystack import indexes
from front.models import Product
class ProductIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
return Product
urls:
path('front/products',views.ProductList.as_view(),name="manage_products"),
template:
{% extends 'base_site.html' %}
{% load render_table from django_tables2 %}
{% block content_title %}Manage Products{% endblock%}
{% block content %}
<form action="" method="get" class="form form-inline">
{{ form }}
<button class="btn btn-default" type="submit">Search</button>
</form>
{% render_table table %}
{% endblock %}
How can I remove that error and provide efficient search abilities to my list views?
Are you sure that you are using haystack.generic_views.SearchView and not haystack.views.SearchView? Please notice that on https://django-haystack.readthedocs.io/en/latest/views_and_forms.html it says:
As of version 2.4 the views in haystack.views.SearchView are deprecated in favor of the new generic views in haystack.generic_views.SearchView which use the standard Django class-based views which are available in every version of Django which is supported by Haystack.
Thus if you're using haystack.views.SearchView then the get_context_data of SingleTableMixin will never be called thus no table will be put in your context (i.e table will be empty). Actually because I dislike the behavior of {% render_table %} when its parameter is empty (it behaves different than the other Django tags/filters i.e it throws an exception while the django ones silently ignore that) I usually put it inside some {% if table %} checks.
UPDATE
It seems that for whatever reason the data is not passed to the table. I am not sure why, I can't test it right now but from a quick look at the source code your implementation should have been working (considering that SearchView has a get_queryset and TableMixin uses the get_queryset to retrieve its data). In any case you try overriding some methods of TableMixin to make sure that the table is properly returned (take a look at TableMixin here: https://django-tables2.readthedocs.io/en/latest/_modules/django_tables2/views.html).
I think the most definite solution would be to just bite the bullet and override get_table yourself. So, try adding something like this to your class:
def get_table(self, **kwargs):
table_class = self.get_table_class()
# I only change this line from the original to make sure that the self.get_queryset() is called to return the data
table = table_class(data=self.get_queryset(), **kwargs)
return RequestConfig(self.request, paginate=self.get_table_pagination(table)).configure(
table
)
One idea that just popped in my mind. Is there a possibility that the get_queryset() method of SearchView returns None due to bad configuration or whatever?
I'm trying to access a url that's like
127.0.0.1:8000/posti/qNwEXBxXQdGI4KlQfoHWOA
However I can't resolve that smalluuid.
This is the error:
NoReverseMatch at /posti/ Reverse for 'detail' with arguments
'(SmallUUID('qNwEXBxXQdGI4KlQfoHWOA'),)' not found. 1 pattern(s)
tried: ['posti/(?P[0-9a-fA-F-]+)/$']
Django has issues trying to resolve it in another view that has a string like this:
from . import views
from django.conf.urls import url
app_name = 'posti'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<slug>[0-9a-fA-F-]+)/$', views.DetailView.as_view(), name='detail'),
My DetailView is this one:
class DetailView(generic.DetailView):
model = Post
template_name = 'posti/detail.html'
slug_field = 'uuid'
def get_queryset(self):
"""
Excludes any questions that aren't published yet.
"""
return Post.objects.all()
I tried rewriting get_object but it didn't do anything. I don't understand if the regex is wrong or if my view has something wrong.
EDIT:
My template on index raised the error above and it had the following code:
{% if posti_list != null %}
<ul>
{% for post in posti_list %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
{% else %}
<p>No posts are available.</p>
{% endif %}
I added slug_url_kwarg = 'uuid' to the DetailView class and now it works BUT now I have a
AttributeError at /posti/qNwEXBxXQdGI4KlQfoHWOA/ Generic detail view
DetailView must be called with either an object pk or a slug.
When I try to access the specific post.
I added slug_url_kwarg = 'uuid' to the DetailView class and now it works BUT now I have a
AttributeError at /posti/qNwEXBxXQdGI4KlQfoHWOA/ Generic detail view DetailView must be called with either an object pk or a slug.
slug_url_kwarg must match your url regex group name (slug in your case, which is default value for slug_url_kwarg), so you shouldn't have changed it
For details look at the piece of Django source code here - https://github.com/django/django/blob/master/django/views/generic/detail.py#L8
Please note: Just to make clear '[app_name]' is a placeholder for the actual app name. Not Django substitution code. Just imagine it says 'Stuff' instead of [app_name], if it's confusing.
My question:
How do I make this more DRY?
There is a lot of code repetition, and there must be a way of unifying some of it. If you do answer, I would be really grateful if you write out explicitly what and why. As a lot of answers assume quite a bit of knowledge and I am trying into good habits in Django coding style and practice. Thank you for your time.
[app_name]/urls.py
from django.conf.urls import url
from . import views
app_name = 'things'
urlpatterns = [
url(r'^cars/$', views.CarThingIndexView.as_view(), name='Car_index'),
url(r'^trees/$', views.TreeThingIndexView.as_view(), name='Tree_index'),
....
]
[app_name]/model.py
from django.db import models
class Tree(models.Model):
"""
Tree
"""
name_text = models.CharField(max_length=200)
def __str__(self):
return self.name_text
class Car(models.Model):
"""
Car
"""
name_text = models.CharField(max_length=200)
def __str__(self):
return self.name_text
[app_name]/view.py
from django.views import generic
from inventory.models import Car, Tree
class CarThingIndexView(generic.ListView):
template_name = '[app_name]/index.html'
context_object_name = 'thing_list'
def get_queryset(self):
return Car.objects.values()
class TreeThingIndexView(generic.ListView):
template_name = '[app_name]/index.html'
context_object_name = 'thing_list'
def get_queryset(self):
return Tree.objects.values()
[app_name]/template/[app_name]/index.html
{% extends '[app_name]/base.html' %}
{% block content %}
{% if thing_list %}
<ul>
{% for item in thing_list %}
<li>
<p>
{{ item }}
</p>
</li>
{% endfor %}
</ul>
{% else %}
<!-- I am pretty sure if there are no objects this will not work, please correct me if I am wrong {{ obj | get_class_name }}. I would like it to read "No Tree/Car are available." -->
<p>No [class_name] are available.</p>
{% endif %}
{% endblock content %}
From my point of view everything is fine and there is no need abstract more in this case. At this point it looks like you can optimize it - but what you've scaffolded does not include any logic. Behind the scenes django already helps you a lot to not repeat yourself here!
When you now start implementing features for your cars and trees the views, models and templates will go in different directions. Next to the name they will have different properties. They act different and were displayed different. This is what usually happens in your application.
If you have a set of models that share a lot of properties and the representation django provides mechanisms like abstract base classes for models properties and methods or the include and extends template tags, if you need to share representations. Regarding view logic that should be shared you might write your own modules or even packages to do whatever you need to do and use them inside your views.
Without real use cases in mind you should not think about how to not repeat yourself at this point.
Just for testing, I have a Python that scripts that enters IP addresses into the database.
I have the following values:
127.0.0.1
127.0.0.1
10.42.1.42
10.42.1.42
10.42.1.42
Model:
class Invalid(models.Model):
access = models.DataTimeField()
ip = models.CharField()
login = models.CharField()
In the template, this is what I have for the drop down list:
<p>Choose:
<select>
{% for item in results %}
<option value="{{ item.ip }}">{{ item.ip }}</option>
{% endfor %}
</select>
<button type="button">Filter</button>
</p>
However, the problem with this method is that it lists all the values. As far as I'm aware, there is no way to create an array/lists although I may be mistaken.
Is there any way to only have distinct values show up in the list, that is:
127.0.0.1
10.42.1.42
assuming results is a queryset :
If you have a backend that supports (postgres) it you could use distinct on the ip field
results.distinct('ip')
Select DISTINCT individual columns in django?
Maybe you could also build a unique collection in python
uniques_ips = {}
for item in results:
if item.ip not in unique_ips:
unique_ips[item.ip] = item
unique_ips.values()
In your views.py file you would subset your queryset to get the distinct values only. Then you would only return these distinct values to your template.
So, using your example, you have a context object called results. You don't define how you came to populate that object, or what your model looks like, so lets assume the following model for your application for the sake of argument:
from django.db import models
class MyModel(models.Model):
ip = models.IPAddressField()
Now, lets take a look at views.py and here we are using Generic Class Based Views:
from django.views.generic.list import ListView
class IpListView(ListView):
def get_queryset(self):
queryset = super(IpListView, self).get_queryset()
unique_ips = queryset.values('ip').distinct()
return unique_ips
Note: You could also put the distinct() method above directly into the urls.py queryset object below...
In your urls.py you would make sure you route IpListView to your template. Something like:
from django.conf.urls import patterns, url
from myapp.models import MyModel
from myapp.views import IpListView
urlpatterns = patterns('',
url(r'^$',
IpListView.as_view(
queryset=MyModel.objects.order_by('ip'),
context_object_name='results',
template_name='myapp/mytemplate.html',
), name='mymodel-ips'
)
)
Note here how results is your context object name. This means that all your ip field values will be available in your template mytemplate.html as:
for r in results:
ip_option = r.ip
And because you have already subset the queryset to only have the unique IPs, you get what you needed. So you just need to loop through:
<p>Choose:
<select>
{% for item in results %}
<option value="{{ item.ip }}">{{ item.ip }}</option>
{% endfor %}
</select>
<button type="button">Filter</button>
</p>
One caveat here: you don't state if you need any other fields from your model. If you do, the queryset I listed above will modify the results you get, and you might not get back what you expect if you wanted to use other values entered to be returned...