Django how to use generic view with UUID instead of PK - django

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

Related

Adding custom table for model in django admin panel

I want to add custom table to my model. I explored this question and found out that I should change change_list_results.html like this:
{% load render_table from django_tables2 %}
{% render_table table %}
Some details.
I implemented table as:
class MyModelTable(tables.Table):
class Meta:
model = MyModel
Then I plugged it to TableView:
class MyModelTableView(tables.SingleTableView):
table_class = MyModelTable
queryset = MyModel.objects.using('my_database').all()
template_name = "admin/change_list_results.html"
After that I redefined url in the head urls.py (that is the error):
urlpatterns = [
path(
'admin/crm/my_model/', MyModelTableView.as_view()
),
path("admin/", admin.site.urls),
]
So if i define this url like this I can't get anything except table, but if I comment it I get error:
ValueError at /admin/crm/my_model/
Expected table or queryset, not str
5 {% render_table table %}
On the admin.py just add your model:
admin.site.register(MyModelTable)
This is enough to make it display on admin

Django - delete button on form not redirecting to the correct path

I have a html template on which multiple messages are posted and can be deleted using a 'delete' button that has been created. My code seeks to search for the id of the item to be deleted and delete it, redirecting to /deleteMessage and concatenating with the id number. I don't fully understand the process and have an error I cannot spot.
html form
<ul>
{% for g in all_items %}
<li> {{ g.content }}
<form action="/deleteMessage/{{g.id}}/"
style="display: inline;"
method="post">{% csrf_token %}
<input type="submit" value="Delete"/>
</form>
</li>
{% endfor %}
</ul>
views.py relevant code
def deleteMessage(request,GuestBookItem_id):
item_to_delete =GuestBookItem.objects.get(id=GuestBookItem_id)
item_to_delete.delete()
return HttpResponseRedirect('/worldguestbook/')
urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('worldguestbook/',worldguestbookView),
path('login/',loginView),
path('addMessage/',addMessage),
path('deleteMessage/',deleteMessage),
]
I imagine it is this line that is causing the error - an error in concatenation and not redirecting to the right path.
**<form action="/deleteMessage/{{g.id}}/"**
Error message:
Page not found (404)
Request Method: POST
Request URL: http://127.0.0.1:8000/deleteMessage/17/
Using the URLconf defined in mysite.urls, Django tried these URL patterns, in this order:
admin/
worldguestbook/
login/
addMessage/
deleteMessage/
The current path, deleteMessage/17/, didn't match any of these.
What I tried:
I tried, in views.py changing this to g.id (instead of GuestBookItems.id) to match with what is in the form, but that didn't work either.
item_to_delete =GuestBookItem.objects.get(id=g_id)
You need to capture GuestBookItem_id in the URL pattern:
path('deleteMessage/<int:GuestBookItem_id>/', deleteMessage),
Note that in Python, you would normally use guest_book_item_id as the variable name. Or since it's the primary key of a model instance, you could use pk. It would be a good idea to use get_object_or_404, so that you get a 404 page when the item does not exist.
You're already using a POST request, which is a good idea when you are changing or deleting objects. You should also check that it's a POST request in the view.
Finally, it's a good idea to reverse URLs instead of hardcoding them. First, you need to add names to your URL patterns, then you can use {% url %} in the template and reverse() or the redirect shortcut in the template.
Putting that together, you get:
<form action="{% url 'delete_message' g.id %}">
urlpatterns = [
path('admin/', admin.site.urls),
path('worldguestbook/', worldguestbookView, name="worldguestbook"),
path('login/', loginView, name="login"),
path('addMessage/', addMessage, name="add_message"),
path('deleteMessage/', deleteMessage, name="delete_message"),
]
path('deleteMessage/<int:pk>/', deleteMessage),
from django.shortcuts import get_object_or_404, redirect
def deleteMessage(request, pk):
if request.method == "POST"
item_to_delete = get_object_or_404(GuestBookItem, pk=pk)
item_to_delete.delete()
return redirect('worldguestbook')

List models containing foreign key in a detail view

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.

Django-filters FilterView shows empty list on page load

So I want to link to a given page that has filters on it, and have it display every item in the table before I click search, and only stop displaying items when bad input is given. My problem is similar to the following problem, with a few differences. Empty result list on django-filter page startup
The differences being the poster's default behavior is my desired behaviour and I am using class based views, not functional views.
My urls:
from django.urls import path
from . import views
app_name = 'advising'
urlpatterns = [
path('', views.MyList.as_view(), name='MyList'),
]
my views:
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, get_object_or_404
from django.views import generic
from django.template import loader
from .models import *
from django_filters.views import FilterView
from .filter import *
class MyList(FilterView):
template_name = 'advising/MyList.html'
context_object_name = 'tables'
filterset_class = MyFilter
def get_queryset(self):
return Table.objects.order_by('Name')
my filter:
import django_filters
from .models import Table
class MyFilter(django_filters.FilterSet):
Name = django_filters.CharFilter(lookup_expr='icontains')
class Meta:
model = Table #The table this form will reference
fields = ["Name"]
my template:
<form method="get">
{{ filter.form.as_p }}
<button type="submit">Search</button>
</form>
{% if tables %}
<ul>
{% for table in tables %}
<li>{{table}}</a></li>
{% endfor %}
</ul>
{% else %}
<p>Nothing to see here!.</p>
{% endif %}
Is there any way to mimic the behavior of searching for an empty string when the page first loads?
To be very specific, I want the url advising/ to have the same behaviour as the url advising/?Name=
Right now advising/ always gives me an empty list
Finally found a post with the same issue I'm having (no idea why it was never coming up on Google) where the issue was solved. It's as simple as adding the line "strict = False" in my view.
This is the question I found that answered it for me:
Display all record on django-filter page startup

Django - NoReverseMatch: Reverse for 'bha' with no arguments not found

project/urls.py - Here, I passed in regex for primary key
from django.urls import path, re_path, include
from contextual import views
urlpatterns = [
url('admin/', admin.site.urls),
path('well_list/', include([
re_path(r'^$', views.WellList_ListView.as_view(), name='well_list'),
re_path(r'^create/', views.AddWell_CreateView.as_view(), name='create'),
re_path(r'^(?P<pk>[-\w]+)/contextual/', include('contextual.urls')),
]))
]
contextual/urls.py
app_name = 'contextual'
urlpatterns = [
re_path(r'^$', base_views.ContextualMainView.as_view(), name='main'),
re_path(r'^bha/$', base_views.BHA_UpdateView.as_view(), name='bha'),
]
views.py
class ContextualMainView(DetailView):
template_name = 'contextual_main.html'
model = models.WellInfo
class WellList_ListView(ListView):
template_name = 'well_list.html'
context_object_name = 'well_info'
model = models.WellInfo
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# get string representation of field names in list
context['fields'] = [field.name for field in models.WellInfo._meta.get_fields()]
# nested list that has all objects' all attribute values
context['well_info_values'] = [[getattr(instance, field) for field in context['fields']] for instance in context['well_info']]
# includes well instance objects & values string list for each well
context['well_info_zipped'] = zip([instance for instance in context['well_info']], context['well_info_values'])
return context
class BHA_UpdateView(UpdateView):
template_name = 'contextual_BHA.html'
model = models.WellInfo
fields = '__all__'
success_url = reverse_lazy('well_list')
well_list.html - primary key is provided in html
<tbody>
{% for well_instance, values in well_info_zipped %}
<tr>
{% for value in values %}
<td>{{ value }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
contextual_main.html - primary key is NOT provided in html
<button type="button" class="btn btn-default" data-container="body" data-toggle="popover">
BHA
</button>
And here is the issue:
http://127.0.0.1:8000/well_list/123412-11-33/contextual/ does not work and gives me error:
NoReverseMatch at /well_list/123412-11-33/contextual/
Reverse for 'bha' with no arguments not found. 1 pattern(s) tried: ['well_list\\/(?P<pk>[-\\w]+)/contextual/bha/$']
However, if I modify my contextual_main.html, and manually pass in a primary key, it works:
<button type="button" class="btn btn-default" data-container="body" data-toggle="popover">
BHA
</button>
It seems that I have to pass in primary key again, if I want to access http://127.0.0.1:8000/well_list/123412-11-33/contextual/bha/
Why does Django make me to pass in pk again, when I already have passed in pk in the parent url? Since I've already passed in pk in well_list.html, which is a parent page of contextual_main.html, my understanding is that I don't have to pass it again.
Is there any way to work this around, like making django to just inherit the primary key from parent, or doing it without re-injecting primary key?
url template tag does not consider current path in the context. It uses django's reverse method to create url from the given parameters.
Quoting the first line from the documentation of url templatetag.
Returns an absolute path reference (a URL without the domain name) matching a given view and optional parameters.
So in your case, you have to pass pk in the contextual_main.html to get the url in the html resolved.
Check docs for more details.
https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#url
Check docs of reverse for more details on it.
https://docs.djangoproject.com/en/2.0/ref/urlresolvers/#reverse