Merging querysets from different models - django

I have 2 models in one app and 1 view. I'm currently pulling information from 1 model perfectly fine. However i wish to pull in another model from the same app and output them both to the same page.
The idea of the page is it being a a news hub so it's pulling through different types of news posts (from one model) and a different type of post which is from the other model.
I'm fairly new to Django so go easy! :) Anyway here is the code:
//VIEWS
def news_home(request):
page_context = details(request, path="news-hub", only_context=True)
recent_posts = NewsPost.objects.filter(live=True, case_study=False).order_by("-posted")[:5]
recent_posts_pages = Paginator(recent_posts, 100)
current_page = request.GET.get("page", 1)
this_page = recent_posts_pages.page(current_page)
notes = BriefingNote.objects.filter(live=True).order_by("-posted")
news_categories = NewsCategory.objects.all()
news_context = {
"recent_posts": this_page.object_list,
"news_categories": news_categories,
"pages": recent_posts_pages,
"note": notes,
}
context = dict(page_context)
context.update(news_context)
return render_to_response('news_hub_REDESIGN.html', context, context_instance=RequestContext(request))
//model 1
class BriefingNote(models.Model):
title = models.CharField(max_length=300)
thumbnail = models.ImageField(upload_to='images/briefing_notes', blank=True)
file = models.FileField(upload_to='files/briefing_notes')
live = models.BooleanField(help_text="The post will only show on the frontend if the 'live' box is checked")
categories = models.ManyToManyField("NewsCategory")
# Dates
posted = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __unicode__(self):
return u"%s" % self.title
// model 2
class NewsPost(models.Model):
title = models.CharField(max_length=400)
slug = models.SlugField(help_text="This will form the URL of the post")
summary = models.TextField(help_text="To be used on the listings pages. Any formatting here will be ignored on the listings page.")
post = models.TextField(blank=True)
#TO BE REMOVED????
thumbnail = models.ImageField(help_text="To be displayed on listings pages", upload_to="images/news", blank=True)
remove_thumbnail = models.BooleanField()
I'm outputting the content on the front end like so:
{% for post in recent_posts %}
<div class='news_first'>
<img class="news_thumb" src="/media/{% if post.article_type %}{{post.article_type.image}}{% endif %}{% if post.news_type %}{{post.news_type.image}}{% endif%}" alt="">
<h3><a href='{{post.get_absolute_url}}'>{% if post.article_type.title %}{{post.title}}{% endif %} <span>{{post.posted|date:"d/m/y"}}</span></a></h3>
<p class='news_summary'>
{% if post.thumbnail %}<a href='{{post.get_absolute_url}}'><img src='{% thumbnail post.thumbnail 120x100 crop upscale %}' alt='{{post.title}}' class='news_thumbnail'/></a>{% endif %}{{post.summary|striptags}} <a href='{{post.get_absolute_url}}'>Read full story »</a>
</p>
<div class='clearboth'></div>
</div>
{% endfor %}
I was thinking perhaps i could output them both within the same forloop however they need to ordered by -posted. So i though this could mess things up.
If you need anymore info please let me know.
Thanks in advance.

I've now solved the problem.
news_hub = list(chain(notes, recent_posts))
news_hub = sorted(
chain(notes, recent_posts),
key = attrgetter('posted'), reverse=True)[:10]

Related

How can I gather all objects, from all models, within one class based view in django 2.2

A friend of mine is trying to gather all of the data from each of his models within one view, to display on one tamplate, with the 'slug' as the URL.
He currently has a class based view that looks like this:
from itertools import chain
class ProductDetailView(DetailView):
queryset1 = BooksProduct.objects.all()
queryset2 = ClothingProduct.objects.all()
queryset3 = FurnitureProduct.objects.all()
queryset4 = DecoratingProduct.objects.all()
queryset = chain(queryset1, queryset2, queryset3, queryset4)
template_name = 'products/detail.html'
The URL looks like this:
urlpatterns =[
path('product-detail/<slug>/', ProductDetailView.as_view(), name='product-detail'),
]
The four different models all look very similar to this:
class BookProduct(models.Model):
slug = models.SlugField(max_length=40)
image = models.ImageField(upload_to='media')
title = models.CharField(max_length=150)
description = models.TextField()
short_description = models.CharField(max_length=300)
price = models.DecimalField(max_digits=5, decimal_places=2)
stock_quantity = models.PositiveIntegerField()
in_stock = models.BooleanField(default=True)
main_category = models.CharField(choices=PRODUCT_CATEGORY_CHOICES, max_length=2, default='FW')
brand = models.CharField(choices=TOY_BRAND_CHOICES, max_length=2, default='NB')
on_sale = models.BooleanField(default=False)
date_added = models.DateTimeField(auto_now_add=True, blank=True)
def __str__(self):
return self.title
class Meta:
ordering = ['-date_added']
def get_absolute_url(self):
return reverse("products:product-detail", kwargs={
'slug': self.slug
})
At the moment, clicking on an individual product to get the product-detail.html view results in this error:
'itertools.chain' object has no attribute 'all'
How is he able to accumulate all of the products from each model into one view?
The DetailView has since been changed to ListView which has made the product details render through to the template, however as soon as you try to refresh the page, or press the back button and re click the product to view the detail page again, it disappears, until he resets the server over again.
The HTML template looks like this:
<section id="detail-view">
<div class="container">
<div class="row">
<div class="col-6">
<img src="{{ product.image.url }}" alt="{{ product.title }}">
</div>
<div class="col-6">
{% for product in object_list %}
<h1>{{ product.title }}</h1>
<strong>£{{ product.price }}</strong>
<hr class="newhr">
ADD TO BASKET
<div class="detail-desc-box">
<p>{{ product.description }}</p>
</div>
{% endfor %}
</div>
</div>
</div>
</section>
And the view now looks like this:
from itertools import chain
class ProductDetailView(ListView):
queryset1 = BooksProduct.objects.all()
queryset2 = ClothingProduct.objects.all()
queryset3 = FurnitureProduct.objects.all()
queryset4 = DecoratingProduct.objects.all()
queryset = chain(queryset1, queryset2, queryset3, queryset4)
template_name = 'products/detail.html'
Kind regards
I don't think you'll be able to use the Generic views out of the box like you're hoping. The reason is because the DetailView is specifically for showing a single model instance. You're attempting to show multiple.
What I'd recommend is to switch this to a ListView, supply an additional url keyword argument to the view to specify the slug, then do one of the following:
Use .union() to make one query to the database and deal with the constraints of that method detailed in the docs.
Refactor your models to use inheritance and make the select based on the common model.
Note:
The error you're encountering currently is because you're effectively changing the type of DetailView.queryset from QuerySet to a generator. The rest of the generic view expects certain members to be present, such as .all().

custom url patterns in Django

I have a website I am trying to build for personal use, and it possesses two id's one for a meeting (where the race is run) and one for the event (the race number). The event id is in the form of "123456_01" and is passed in the model as a primary key for the Event model, as seen below...
class Event(models.Model):
meeting = models.CharField(max_length=500)
meetingID = models.ForeignKey(Meeting, on_delete='CASCADE', related_name='races')
eventID = models.CharField(max_length=300, primary_key=True)
venue = models.CharField(max_length=600, null=True)
race_no = models.CharField(max_length=2)
event_time = models.TimeField()
status = models.CharField(max_length=100)
distance = models.CharField(max_length=600)
I currently have the views file set up as follows:
class EventDetailView(DetailView,LoginRequiredMixin):
context_object_name = 'race_detail'
template_name = 'event.html'
model = models.Event
slug_url_kwarg = 'eventID'
I also have my front end set up so that at present when I click on a certain race, it automatically navigates to the page with the link http://127.0.0.1:8000/app/123456_01/, so that part is working through this config in the HTML:
{% url 'bettingUI:race' eventID=events.eventID %}
the problem I seem to be having is with the configuration of the urls.py file and possibly something I am missing in the views.py file.
my urls.py file is set up as follows :
from django.urls import path, include
from . import views
app_name = 'bettingUI'
urlpatterns = [
path('',views.DashListView.as_view(),name='dashboard'),
path('<eventID>/', views.EventDetailView.as_view(), name='race'),
]
I thought from reading the docs that I need to use a slug because of the '_' character in the ID I am passing in but I am constantly getting an error in the browser stating that it can not resolve keyword 'slug' into the field. Choices are: dro_eventID, dro_meetingID, dro_meetingID_id, event_time, meeting, race_no, runners, status, venue ( **the fields of the model). If I change the urls.py file to the below, I get the same error:
path('<slug:eventID>/', views.EventDetailView.as_view(), name='race'),
I am a bit lost here so would love some guidance.
Thank you.
I worked it out, the answer is to input <slug:pk>
but now I am getting an error at my dashpage (the page i land at to click through to the race page):
NoReverseMatch at /app/
Reverse for 'race' with keyword arguments '{'eventID': '1216859_01'}' not found. 1 pattern(s) tried: ['app/(?P<pk>[-a-zA-Z0-9_]+)/$']
So I give it again now the working version:
First you should add a slug field to your Event Model and this will let you use slug, so your model will look like this:
from django.utils.text import slugify
class Event(models.Model):
meeting = models.CharField(max_length=500)
meetingID = models.ForeignKey(Meeting, on_delete='CASCADE', related_name='races')
eventID = models.CharField(max_length=300, primary_key=True)
venue = models.CharField(max_length=600, null=True)
race_no = models.CharField(max_length=2)
event_time = models.TimeField(null=True)
status = models.CharField(max_length=100, null=True)
distance = models.CharField(max_length=600, null=True)
slug = models.SlugField(max_length=50, null=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.eventID, allow_unicode=True)
return super(Event, self).save(*args, **kwargs)
Notice the save() function and in that we added a slugify() method to slugify the eventID field at event savings.
Then your views should look like these:
from .models import Event, Meeting
class EventList(ListView):
model = Event
template_name = 'event_list.html'
context_object_name = 'race_list'
class EventDetailView(DetailView,LoginRequiredMixin):
context_object_name = 'race_detail'
template_name = 'myusers1/event.html' # this could be only event.html if the template is in yourapp/templates/ folder directly
model = Event
slug_url_kwarg = 'slug'
Notice in the above view that we now use actually the default slug definition.
I put the listview url under races/ sub-url but you can put it anywhere you want. And in your urls.py you can now use the slug values correctly like:
path('races/<slug:slug>/', views.EventDetailView.as_view(), name='race'),
path('races/', views.EventList.as_view(), name='race_list'),
In my trial app the templates look like the followings: listview template:
{% extends 'myusers1/base.html' %}
{% block content %}
<div class"container">
<div class="col col-lg-2">
<h2>Races</h2>
<ul>
{% for race in race_list %}
<div class="col-xs-12 .col-md-8"><li> {{ race.venue }} </li></div>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}
And the detail template looks like this:
{% extends 'myusers1/base.html' %}
{% block content %}
<div class"container">
<div class="col col-lg-2">
<h2>Race Details</h2>
<div class="col-xs-12 .col-md-8"> <h4>Venue name: </h4> {{ race_detail.venue}} </div>
<div class="col-xs-12 .col-md-8"> <h4>Event ID: </h4> {{ race_detail.eventID }} </div>
<div class="col-xs-12 .col-md-8"> <h4>Meeting name: </h4> {{ race_detail.meeting }} </div>
<div class="col-xs-12 .col-md-8"> <h4>Meeting ID: </h4> {{ race_detail.meetingID.id }} </div>
</div>
</div>
{% endblock %}
And the visual result about how dynamic urls work using the above:
I hope that the above will help you to finalize your app list and details view now. Cheers.
I think I found a solution here try this:
url.py:
path('<slug:eventID>/', views.EventDetailView.as_view(), name='race')
Now you can simple get the instance of Event in your EventDetailView generic view by using get_object method like this:
class EventDetailView(DetailView, LoginRequiredMixin):
context_object_name = 'race_detail'
template_name = 'event.html'
model = models.Event
def get_object(self):
e1 = Event.objects.get(eventID=self.kwargs['eventID'])
print (e1.eventID) # or e1.pk gives: 123456_01
return e1
You can also change your eventID from CharField to SlugField. And still have it working.

Filter a Django form select element based on a previously selected element

Let's consider the following models
models.py
Class Brand(models.Model):
company_name = models.CharField(max_length=100)
class CarModel(models.Model):
brand = models.ForeignKey(Brand)
name = models.CharField(max_length=100)
Class FleetCars(models.Model):
model_car = models.Foreignkey(CarModel)
What is the best way to solve this problem in django?
Suppose a form (for insertions in FleetCars) consists of two select elements, like this:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<br />Brand:
<select>
<option value="Brand1">Brand1</option>
<option value="Brand2">Brand2</option>
</select>
<br />
<br />Model:
<select>
<option value="Model1_B1">Model1_B1</option>
<option value="Model1_B2">Model1_B2</option>
</select>
</body>
</html>
In this case, I want the options in the second select to depend on the value selected in the first. For example, if the user chose Brand1 for a Brand in the first select, the second select would be filtered with only cars whose Brand was Brand1, that is, only "Model1_B1".
Obs.
I saw many solutions with forms.ModelChoiceField, but only works with edit and since the user do not change the brand.
After hours and hours of research, without success, I decided to try to solve on my own. The solution that I found maybe don't be the best or the more elegant, but is working. (For download full Django project, click on this repo => https://github.com/Sidon/djfkf/.)
models.py
from django.db import models
class Brand(models.Model):
company_name = models.CharField(max_length=100)
def __str__(self):
return self.company_name
class Car(models.Model):
brand = models.ForeignKey(Brand)
name = models.CharField(max_length=100)
def brand_name(self):
return self.brand.company_name
def __str__(self):
return self.name
class Fleet(models.Model):
car = models.ForeignKey(Car)
description = models.CharField(max_length=100)
def car_name(self):
return self.car.name
def brand(self):
return self.car.brand.company_name
def __str__(self):
return self.description
The goal is to register cars on the fleet. The only fields that are will be recorded: Car (foreign key) and description. On the form, there will be one select element for brands that will work just only as a helper for to filter the car's combo box.
forms.py
import json
from django import forms
from .models import *
class RegCarForm(forms.ModelForm):
dcars = {}
list_cars = []
for car in Car.objects.all():
if car.brand.company_name in dcars:
dcars[car.brand.company_name].append(car.name)
else:
dcars[car.brand.company_name] = [car.name]
list_cars.append((car.name,car.name))
brands = [str(brand) for brand in Brand.objects.all()]
brand_select = forms.ChoiceField(choices=([(brand, brand) for brand in brands]))
car_select = forms.ChoiceField(choices=(list_cars))
brands = json.dumps(brands)
cars = json.dumps(dcars)
class Meta:
model = Fleet
fields = ('brand_select', 'car_select', 'description',)
RegCarForm is a form for register cars, there are three fields: brand_select, car_select, and description. In addition, I defined two JSON attributes: 1) a dictionary whose keys are brands (strings) and values are lists of respective's cars and 2) A list of strings that represent the brands. Those two attributes will work as helpers for JS functions.
views.py
from django.shortcuts import render
from .forms import RegCarForm
from .models import *
def regcar(request):
if request.method == 'POST':
car_form = RegCarForm(data=request.POST)
if car_form.is_valid():
cdata = car_form.cleaned_data.get
car_selected = Car.objects.filter(name=cdata('car_select'))
reg1 = Fleet(car_id=car_selected[0].id, description=cdata('description'))
reg1.save()
else:
print ('Invalid')
else:
car_form = RegCarForm()
return render(request, 'core/regcar.html', {'car_form': car_form})
The view is practically auto-explanatory. Assigns the Form to the car_form variable, render the template core/regcar.html and, after Post, make the validation of the form and save the data.
regcar.html (template django)
{% extends "base.html" %}
{% block head %}
{% endblock %}
{% block content %}
<h1>Registering cars on the fleet. <br />(Populate one drop down based on selection in another)</h1>
<p>Change the contents of drop down Car based on the selection in dropdown Brand, using Django-forms + Javascritp</p>
<div class="select-style">
<form action="." method="post">
{% csrf_token %}
{{ car_form.as_p }}
<p><input type="submit" value="Register a car"></p>
</form>
</div>
{% endblock %}
{% block js %}
{% include "js1.html" %}
{% endblock %}
The template only just renders the form and load the script JS. Nothing else.
Finally, the js script, that makes the hard work.
{% block js %}
<script language="javascript">
$('#id_brand_select').change(function() {populateCar(this)});
$('#id_description').addClass('descriptions');
cars = {{ car_form.cars | safe }}
brands = {{ car_form.brands | safe}};
populateBrand();
$("#id_car_select").empty();
$("#id_car_select").append('<option value="" disabled selected>First select a brand</option>');
function populateBrand() {
$('#id_brand_select').empty();
$("#id_brand_select").append('<option value="" disabled selected>Select your option</option>');
$.each(brands, function(v) {
$('#id_brand_select')
.append($("<option></option>")
.attr("value", brands[v])
.text(brands[v]));
});
}
function populateCar(event) {
brand = $("#id_brand_select option:selected").text();
$("#id_car_select").empty();
$("#id_car_select").append('<option value="" disabled selected>Select your option</option>');
for (let [b, bcars] of Object.entries(cars)) {
if (b == brand) {
//alert(b);
for (car in bcars) {
$('#id_car_select')
.append($("<option></option>")
.attr("value", bcars[car])
.text(bcars[car]));
}
}
}
}
</script>
{% endblock %}
When the document is loaded, this script assigns the change event of brand_select (combo for selection of brand) to the function poplulateCar, assign the form's JASON attributes (cars and brands) to a JS variables and call the populateBrand function.
Links:
Full project in Django:
https://github.com/Sidon/djfkf/
class Country(models.Model):
country_name=models.CharField(max_length=10, blank=True, null=True)
class State(models.Model):
state_name=models.CharField(max_length=10, blank=True, null=True)
class MyCustomModal(models.Model):
country = models.ForeignKey(Country, on_delete=models.CASCADE, null=True, blank=True)
state = models.ForeignKey(State, on_delete=models.CASCADE, null=True, blank=True)
Here is my Form
class MyCustomForm(forms.ModelForm):
class Meta:
model = MyCustomModal
fields = [
'country',
'state',
]
def __init__(self, *args, **kwargs):
super(MyCustomForm, self).__init__(*args, **kwargs)
self.fields['country'] = forms.ChoiceField(choices=[('1','india'),('2','US')])
self.fields['state'].queryset = State.objects.filter(pk=2)

django blog no display - beginner

I am running django 1.4.6 with python 2.7 and I am trying to get a test blog working to help my coding skills - but I have run into some trouble and now I am very confused, so I must ask for some help.
Here is my model.py entry:
class BlogPostDetails(models.Model, FillableModelWithLanguageVersion):
blog_post_title = models.CharField(max_length=100)
blog_post_date_published = models.DateTimeField()
blog_post_author = models.CharField(max_length=50)
blog_post_body = models.TextField()
blog_post_timestamp_added = models.DateTimeField(auto_now_add=True, auto_now=False)
blog_post_timestamp_updated = models.DateTimeField(auto_now=True, auto_now_add=False)
def __unicode__(self):
return self.blog_post_title
Here is my url.py entry:
url(r'^details/blog_list/$', 'blog_post_list', name='blog_post_list'),
Here is my views.py entry:
def blog_post_list(request):
language_versions = get_language_versions(user=request.user)
blog_posts = BlogPostDetails.objects.filter()
return render(request, 'core/details/blog_list.html', {
'display_default_language': display_default_language(request.user),
'languages': LANGUAGES,
'language_versions': language_versions,
'language_versions_num': len(language_versions),
'popover_string_length_20': settings.POPOVER_STRING_LENGTH_20,
'popover_string_length_500': settings.POPOVER_STRING_LENGTH_500,
})
Here is my template loop where I thought the blog details would be displayed:
{% block page_content %}
{% for blog_post in blog_posts %}
{{ forloop.counter }}<br />
{{ blog_posts.blog_post_body|safe|truncatechars:popover_string_length_500|linebreaksbr }} <br />
{% endfor %}
{% endblock %}
I have added the blog to the admin and added several blog records.
My problem is what have I done incorrectly because nothing is showing in the template. I really need some guidance here.
You haven't appended blog_posts to your context at your view:
def blog_post_list(request):
language_versions = get_language_versions(user=request.user)
blog_posts = BlogPostDetails.objects.filter()
return render(request, 'core/details/blog_list.html', {
'blog_posts': blog_posts, # HERE YOU GO
'display_default_language': display_default_language(request.user),
'languages': LANGUAGES,
'language_versions': language_versions,
'language_versions_num': len(language_versions),
'popover_string_length_20': settings.POPOVER_STRING_LENGTH_20,
'popover_string_length_500': settings.POPOVER_STRING_LENGTH_500,
})

django template behaving odd, or is it?

I have a class Explore, which is used to get all the photos of other classes (like Photo, ProfilePicture, etc). And in the Explore class I have a method (get_rendered_html) which is used to render the objects in the template (explore_photo.html), so that I can pass it on to the main page (index.html).
Update:
Please let me elaborate my question. Suppose there are three Users A, B and C. If A is the current user, I want all the photos of A to be displayed, including other PUBLIC photos of B and C.
I tried this:
{% if object.display == 'P' or user == object.user %}
{% if object.display == 'P' or request.user == object.user %}
If I do it this way, only the public images are displayed, even though the current user is the object's user. And if I do this way:
{% if object.display == 'P' or user != object.user %}
All the image are displayed.
What may be the cause here? Please help me solve this problem. I would really appreciate your suggestion or advice. Thank you.
class Photo(models.Model):
ONLYME = 'O'
FRIENDS = 'F'
PUBLIC = 'P'
CHOICES = (
(ONLYME, "Me"),
(FRIENDS, "Friends"),
(PUBLIC, "Public"),
)
display = models.CharField(default='F', max_length=1, choices=CHOICES, blank=True, null=True)
user = models.ForeignKey(User)
description = models.TextField()
pub_date = models.DateTimeField(auto_now=True, auto_now_add=False)
update = models.DateTimeField(auto_now=False, auto_now_add=True)
image = models.ImageField(upload_to=get_upload_file_name, blank=True)
class Meta:
ordering = ['-pub_date']
verbose_name = 'photo'
verbose_name_plural = 'photos'
def __unicode__(self):
return self.description
class Explore(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
pub_date = models.DateTimeField()
content_object = generic.GenericForeignKey('content_type','object_id')
def get_rendered_html(self):
template_name = 'explore_photo.html'
return mark_safe(render_to_string(template_name, {'object': self.content_object}))
explore_photo.html:
{% if object.display == 'P' or user == object.user %}
<div class="explore_photo">
<img src="media/{{ object.image }}">
<p class="photo_date">{{ object.pub_date|date:"F jS Y, P" }}</p>
<p class="photo_description">{{object.description}}</p>
<p class="photo_user">{{ object.user }}</p>
</div>
{% endif %}
index.html:
<body>
<h1>Explore</h1>
<div id="explore">
{% for photo in photos %}
<p>1</p>
{{ photo.get_rendered_html }}
<hr>
{% endfor %}
</div>
</body>
Update:
def explore(request):
"""
Return all the photos of the Explore Class in the Explore url
"""
photos = Explore.objects.all()
return render(request, 'index.html', {'photos':photos})
Disclaimer: I'm not familiar with GenericForeignKey and I don't understand why you have a user FK in Explore. I'm sure of one thing though: the filtering should be done in the view, not in the template! Use a something like that:
photos = Explore.objects.filter(content_object__display='P',
content_object__user=request.user)
# But as I said, I don't understand this Explore model, so I would probably
# delete it and use Photo directly
photos = Photo.objects.filter(display='P', user=request.user)
Now you can remove the condition in your template and it should work.
EDIT
My bad, you need a OR query, not a AND. Use Q objects in the filter instead.
from django.db.models import Q
photos = Photo.objects.filter(Q(display='P') | Q(user=request.user))
# Idem for the Explore model or any other model where you need a OR or a
# complex query.
You might want to compare User.pk rather than User objects.
Edit :
In this line : {% if object.display == 'P' or user == object.user %} and the others involving user, object.user or request.user, django will not guarantee that the objects will be equal, even if they should be. To compare an object against another, you want to compare some values they hold, comparing primary keys (user.pk) is a convention.
{% if object.display == 'P' or user.pk == object.user.pk %} should work fine. (well, i honestly suppose it will)