Django: path doesn't find the right primary key in url path - python

I'm using django2 and I get an error when I access this url:
http://127.0.0.1:8000/hotes/12/access/7/update
I get an error 404 "None access object was found"
To make a long story short:
I want to update an object linked to another. To do so, I have to send through the link, both primary keys (12 and 7 in the url). Also, I use the generic view "UpdateView" given by Django.
This is the path concerned in my project.urls:
urlpatterns = [
path('hotes/<int:pk>/access/<int:access_pk>/update/',views.AccessUpdateView.as_view(), name='access_update'),
path('hotes/add',views.host_add, name='host_add'),
path('hotes/<int:pk>/', include([
path('edit',views.HostUpdateView.as_view(), name='host_update'),
path('delete',views.host_delete, name='host_delete'),
])),
path('hotes/<int:pk>/add/', include([
path('access',views.access_add, name='access_add'),
path('oncall',views.onCall_add, name='onCall_add'),
path('network',views.network_add, name='network_add'),
])),
path('hotes/<int:pk>/',views.host_view, name='host_view'),
path('hotes/',views.hosts_view, name='hosts_view'),
path('', views.home, name='home'),
path('admin/', admin.site.urls),
]
I want the second primary key to be used in my view "AccessUpdateView".
This is a part of my models.py:
class Host(models.Model):
name = models.CharField(max_length=30, unique=True)
usage = models.CharField(max_length=30, blank=True)
function = models.CharField(max_length=30, blank=True)
production = models.NullBooleanField(blank=True, null=True)
place = models.CharField(max_length=30, blank=True)
type = models.CharField(max_length=30, blank=True)
processor = models.DecimalField(max_digits=3, decimal_places=2, null=True, blank=True)
storage = models.CharField(max_length=10, blank=True)
memory = models.CharField(max_length=10, blank=True)
dns_inner = models.CharField(max_length=50, blank=True)
dns_extern = models.CharField(max_length=50, blank=True)
os = models.ForeignKey(Os, null=True, related_name='hosts', on_delete=models.SET_NULL, blank=True)
class Access(models.Model):
service = models.CharField(max_length=20)
client_access = models.NullBooleanField(blank=True, null=True)
ip = models.GenericIPAddressField()
login = models.CharField(max_length=30, blank=True)
password = models.CharField(max_length=50, blank=True)
host = models.ForeignKey(Host, related_name='access', on_delete=models.CASCADE)
As you can see on host can have multiple access but an access in linked to only one host.
This is the view concerned:
class AccessUpdateView(UpdateView):
model = Access
fields = ('service','client_access','ip','login','password', )
template_name = 'access_update.html'
pk_url_kwarg = 'access_pk'
context_object_name = 'access'
def form_valid(self, form):
access = form.save(commit=False)
host_id = self.kwargs['pk']
access.host_id = host_id
access.save()
return redirect('host_view', pk=host_id)
EDIT: new error when I try to access the url:
NoReverseMatch at /hotes/12/access/7/update/
Reverse for 'host_view' with arguments '('',)' not found. 1 pattern(s) tried: ['hotes\/(?P[0-9]+)\/$']
EDIT:
The error was coming from "access_update.html"
I removed the href in the Hote link which contained {% url host.pk %}
{% extends 'base.html' %}
{% load widget_tweaks %}
{% block title %}Modifier Acces{% endblock %}
{% block breadcrumb %}
<li class="breadcrumb-item">Hotes</li>
<li class="breadcrumb-item">Hote</li>
<li class="breadcrumb-item active">Modification Acces</li>
{% endblock %}
{% block contenu %}
<form method="post" novalidate>
{% csrf_token %}
{% include 'includes/form.html' %}
<button type="submit" class="btn btn-success">Modifier</button>
</form>
{% endblock %}
The question is what is the right way to write the pk of the host in the url?
(host_id doesn't work)

If you want to use access_pk, then you should set pk_url_kwarg = 'access_pk' in the view.
In your form_valid method, you are using host without defining it. If pk from the URL is the host id, then you can access it with self.kwargs['pk'].
def form_valid(self, form):
access = form.save(commit=False)
host_id = self.kwargs['pk']
access.host_id = host_id
access.save()
return redirect('host_view', pk=host_id)
Inside the template for the AccessUpdateView, you have access to access since that is the object that is being updated. If you want to use the host or its id, you should access it via access.
{% url 'host_view' access.host_id %}

Related

Objects have the same ID on production in Django App

I have project in Python Django. It has three models: Project, Files and Agreement.
class Project(models.Model):
project_name = models.CharField(max_length=50)
client_name = models.CharField(max_length=50)
agreement_number = models.CharField(max_length=20)
brief_status = models.CharField(max_length=20, choices=BRIEF_CHOICES, default='nieuzupelniony')
agreement_status = models.CharField(max_length=20, choices=AGREEMENT_CHOICES, default='niedostarczona')
resources_status = models.CharField(max_length=20, choices=RESOURCES_CHOICES, default='niedostarczone')
payment_status = models.CharField(max_length=20, choices=PAYMENT_CHOICES, default='nieoplacone')
message = models.CharField(max_length=200, default='Brak wiadomości')
project_date = models.CharField(max_length=10)
status = models.CharField(max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="project")
modifications = models.CharField(max_length=15, default='2')
corrections = models.CharField(max_length=15, default='3')
def __str__(self):
return self.project_name
class Files(models.Model):
name = models.CharField(max_length=50)
upload = models.FileField(upload_to='uploads/', validators=[validate_file_extension])
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="files")
project = models.ForeignKey(Project, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Agreement(models.Model):
name = models.CharField(max_length=50)
upload = models.FileField(upload_to='uploads/', validators=[validate_file_extension])
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="agreement")
project = models.ForeignKey(Project, on_delete=models.CASCADE)
def __str__(self):
return self.name
In app we have an admin and users. Every user has welcome.html page which display every user project, every user agreement and objects from one additional model which isn't important. When user click on project (user goes to project_detail.html), he can see information about project and there are all files from this project.
def panel(request):
if request.user.is_authenticated:
queryset = Project.objects.filter(user=request.user)
queryset2 = Invoice.objects.filter(user=request.user)
queryset3 = Files.objects.filter(user=request.user)
queryset4 = Agreement.objects.filter(user=request.user)
return render(request, 'projects/welcome.html', {'project': queryset, 'invoice': queryset2, 'files': queryset3, 'agreement': queryset4})
else:
return render(request, 'projects/welcome.html')
Admin has user_detail.html page, and there are all files and projects and agreements for this user.
class UserDetailView(DetailView):
model = User
template_name = 'projects/user_detail.html'
context_object_name = 'user'
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)
context_data['invoice'] = Invoice.objects.all()
context_data['project'] = Project.objects.all()
context_data['files'] = Files.objects.all()
context_data['agreement'] = Agreement.objects.all()
return context_data
Here is a part of my template.
<div class="block bg-white rounded shadow-2xl text-indigo-800 m-2 p-4 w-full h-full">
<p class="text-3xl text-left mb-2 pb-2 border-b">Pliki</p>
{% for files in files.all %}
{% if files.user == user %}
<div class="flex justify-between mb-2 pb-2 border-b pr-32">
{{files.name}}
<p class="text-xl font-thin text-indigo-800 hover:text-indigo-600 text-left mb-2">{{files.project}}</p>
</div>
{% endif %}
{% endfor %}
</div>
Unfortunetly when I tried (as admin) edit any of file I've got an error:
Exception Type: MultipleObjectsReturned
Exception Value:
get() returned more than one Files -- it returned 8!
So to each project you can assign file and agreement. I have problem with my database. On my local environment everything is fine. On local SQlite3 every file object has unique ID, unfortunetly on my producton, on phpMyAdmin every object has the same ID = 0. Where I can find error? I tried working with my models and database, unfortunetly I can't find what is going on. So now I cannot update file for my user even by my Django Admin Dashboard, because I've got an error.
Hi you have a problem in your template you use files in files.all so when you do files.project you get more than one
{% for files in files.all %}
you need to do :
{% for file in files.all %}
....
{{file.project}}</p>

Using Django DeleteView and getting a 404 after delete confirmation

After clicking on "confirm" in my organism_delete.html form, I used to be redirected back to the list of organisms (organism_list.html template) as specified in the view. But now I get a 404 error instead.
Page not found (404) Request Method: GET Request
URL: http://localhost:8000/library/organisms/ABC1233/delete/post?csrfmiddlewaretoken=Rdk575IEp5bbvrriJ1szlYNjmq8V1DvuYzNWEWz07s78IJSal9foHdkvxwcimIEp
Using the URLconf defined in itslibrary.urls, Django tried these URL
patterns, in this order:
admin/
accounts/ [name='home']
library/ organisms/ [name='organism_list']
library/ organisms/new/ [name='organism_new']
library/ organisms/ [name='organism_detail']
library/ organisms//update/ [name='organism_update']
library/ organisms//delete/ [name='organism_delete']
^media/(?P.*)$ The current path,
library/organisms/ABC1233/delete/post, didn’t match any of these.
Two things that stand out to me is first that the error says it's a GET request, not a POST as the form specifies.
And second is why is it trying to get to .../delete/post...?
It might be important to know that I changed my model and added "Primary Key = True" to a unique CharField, and I've been modifying the rest of the app to match that. It may not be related because I can list the organisms and I can get to the delete page, I just can't submit it.
I don't know how to debug this, it seems to be hidden behind the Django magic, any guidance will be very appreciated.
Code below:
Models.py:
#Organism
class Organism(models.Model):
genbank = models.CharField(max_length = 10, primary_key=True, unique=True)
genus = models.CharField(max_length = 50)
species = models.CharField(max_length = 50)
strain = models.CharField(max_length = 50)
organism_sequence = models.TextField()
created_at = models.DateTimeField(auto_now_add = True)
fasta = models.FileField(upload_to='organism_fasta/', null=True, blank=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.SET_NULL, null=True)
def __str__(self):
return self.genus[:10] + ', ' + self.species[:10] + ', ' + self.strain[:10]
def get_absolute_url(self):
return reverse('organism_detail', args=[str(self.genbank)])
#Motif
class Motif(models.Model):
organism = models.ForeignKey('Organism', on_delete = models.CASCADE, related_name= "motifs")
region_name = models.CharField(max_length = 15, choices = MOTIF_CHOICES)
motif_sequence = models.CharField(max_length = 600)
structure_image = models.ImageField(upload_to='structures/', blank=True, null=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.SET_NULL, null=True)
created_at = models.DateTimeField(auto_now_add = True)
def __str__(self):
return self.region_name[:10]
app urls.py
urlpatterns = [
path('organisms/', views.OrganismListView.as_view(), name='organism_list'),
path('organisms/new/', views.OrganismCreateView.as_view(), name='organism_new'),
path('organisms/<pk>', views.OrganismDetailView.as_view(), name='organism_detail'),
path('organisms/<pk>/update/', views.OrganismUpdateView.as_view(), name='organism_update'),
path('organisms/<pk>/delete/', views.OrganismDeleteView.as_view(), name='organism_delete'),
]
Delete View in views.py:
#Delete
class OrganismDeleteView(LoginRequiredMixin, DeleteView):
model = Organism
success_url = '/library/organisms'
template_name = 'library/organism_delete.html'
login_url = "/login"
organism_delete.html template
{% extends "base.html" %}
{% block content %}
<form action="post">
{% csrf_token %}
<p>Are you sure you want to delete {{organism.genus}} {{organism.species}} {{organism.strain}}?</p>
<p>This cannot be undone!</p>
<input type="submit" class="btn btn-danger" value="confirm"/>
</form>
{% endblock content %}
The method="…" of the form is "POST", not the action="…":
<form method="post">
{% csrf_token %}
<p>Are you sure you want to delete {{organism.genus}} {{organism.species}} {{organism.strain}}?</p>
<p>This cannot be undone!</p>
<input type="submit" class="btn btn-danger" value="confirm"/>
</form>
It might be better to work with reverse_lazy(…) [Django-doc] to determine the path of the success_url:
from django.urls import reverse_lazy
class OrganismDeleteView(LoginRequiredMixin, DeleteView):
model = Organism
success_url = reverse_lazy('organism_list')
template_name = 'library/organism_delete.html'
login_url = '/login'
When you do <form action="post" ..., you are asking the form to "append post to the current URL, using a GET request, and go the endpoint with 'your data'".
More details here: https://www.w3schools.com/tags/att_form_action.asp
I am not sure how will you manage to make the page call again the same page (maybe omit action="...", but you should add (change) method="..."; from the implicit method="get", make it a method="post" instead.
Without being a django connoisseur myself, it would be nice if you could "easily" teach django to listen to the method="delete" instead of POST, as it will make your API a tad more semantic.

Django - filtering by user

I want to render data from the Route model that belongs to the Driver in their 'accounts' page - so displaying the leave_from, destination etc data they have saved in the database so far.
Models.py:
class Driver(models.Model):
user = models.OneToOneField(User, default=1)
first_name = models.CharField(max_length=120, blank=True, null=True)
last_name = models.CharField(max_length=120, blank=True, null=True)
tel = models.CharField(max_length=120, blank=True, null=True)
slug = models.SlugField(max_length=120, unique=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
def __str__(self):
return self.user.username
def get_absolute_url(self):
return reverse("account", kwargs={"slug": self.slug})
def save(self, *args, **kwargs):
self.slug = slugify(self.first_name)
super(Driver, self).save(*args, **kwargs)
class Route(models.Model):
leave_from = models.CharField(max_length=120, blank=True, null=True)
destination = models.CharField(max_length=120, blank=True, null=True)
date = models.DateField(auto_now_add=False, auto_now=False)
time = models.TimeField(auto_now_add=False, auto_now=False)
driver = models.ForeignKey(Driver, on_delete=models.CASCADE)
def __str__(self):
return self.leave_from
I've played with various querysets and the below is the closest to getting there (I think... I'm new to coding and Django).
Views.py:
def route(request, slug):
routedetails = Driver.objects.filter(route=request.user.driver.route_set.all())
context = {
"routedetails": routedetails,
}
return render(request, "route.html", context)
With that I am able to get user to display the same number of instances of data in Route for that Driver.
Template:
{% for route in routedetails %}
<p>{{ route.user }}</p>
{% endfor %}
I've tried all different variations but I feel this has got me the closest as it is at least returning the user the same number of times there is data in Route for this user. In this case there are 2 routes saved in Route and so the username is returned twice. I have tested on other users and it always matches.
I've looked everywhere and this is as far as I've been able to get so appreciate any help.
If you want Route details it is best to query the Route model directly:
routedetails = Route.objects.filter(driver__user=request.user)
You can then iterate through the Route objects in your template:
{% for route in routedetails %}
<p>{{ route.leave_from }}</p>
<p>{{ route.destination }}</p>
...
{% endfor %}
Pocket Kings' solution is great and should be accepted. This is an example if you want to show routes for multiple drivers (admin page?) in order to avoid N+1 queries. This pre-fetches all the routes associated to the drivers and adds an attribute routes to each driver with their specific routes, so that it would eliminate the unneeded SQL queries later.
from django.db.models import Prefetch
drivers = Driver.objects.all()
queryset = drivers.prefetch_related(Prefetch('route_set', queryset=Route.objects.filter(driver_id__in=drivers), to_attr='routes'))
Template
{% for driver in drivers %}
{% for route in driver.routes %}
<p>{{ route.leave_from }}</p>
<p>{{ route.destination }}</p>
...
{% endfor %}
{% endfor %}
To get logged in driver's routes, the simplest approach is.
views.py
routes = request.user.driver.route_set.all()
template
{% for route in routes %}
{{ route.leave_from }}
{{ route.destination }}
{% endfor %}

Django 1.8.3: Showing many-to-many additional fields in template

I have the following problem: A project can have multiple Roles, How do I show the roles field in a template? project.role shows blank and the problem is even after using _set.all in the template, I still do not get the contents from the database to show.
Model.py
class Project(models.Model):
"""
Information for each Project
"""
project_name = models.CharField(max_length=255, blank=True, null=True, unique=True)
project_description = models.TextField(null=True, blank=True)
project_url = models.URLField(max_length=200, null=True, blank=True)
#For Admin Purposes and filtering, to keep track of new and old in the database by administrative users
date_added = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date added'))
last_modified = models.DateTimeField(auto_now=True, null=True, blank=True, verbose_name=_('Last modified'))
class Role(models.Model):
"""
Information for Role
"""
role = models.CharField(max_length=255, blank=True, null=True)
project_role = models.ManyToManyField(Project)
View.py
class ProjectView(ListView):
template_name = '_projects.html'
model = Project
def get_context_data(self, **kwargs):
context = super(ProjectView, self).get_context_data(**kwargs)
context['projects'] = Project.objects.all()
return context
Template
{% for project_info in projects %}
{{project_info.project_name}}
</br>
{{project_info.project_description}}
</br>
{{project_info.project_url}}
</br>
{% for rolling in project_info.project_role_set.all %}
{{rolling}}
{% endfor %}
{% endfor %}
Try this:
{% for rolling in project_info.role_set.all %}
{{rolling}}
{% endfor %}

Display data from models in website

I am new to Django. I want to display latest data entered from models to the website.
models.py
class Service(models.Model):
service_name = models.CharField(max_length=200)
service_code = models.IntegerField(default=0, unique=True)
views.py
def Latest(return):
latest_services = Service.objects.order_by('service_name')
index.html
{{ service_name }}
Latest Services Goes here
When I run the code nothing is displayed!
You need to iterate over the queryset:
<ul>
{% for service in latest_services %}
<li>{{ service.service_name }}</li>
{% endfor %}
</ul>
Anyway, if you want to display the latest entries, you should add a new field to your model with the date. For example:
class Service(models.Model):
created_on = models.DateTimeField(auto_now=True)
service_name = models.CharField(max_length=200)
service_code = models.IntegerField(default=0, unique=True)
And then in your query:
latest_services = Service.objects.order_by('created_on')

Categories