Django: Splitting one app into multiple: Templates, Proxy Models & ForeignKeys - python

To keep my project cleaner I decided (maybe wrongly) to split my one Django app into two. One app for the management of information, the other for display. And for this I thought using Django Proxy Models in the display App would be the best way. However, I've come across a problem with the ForeignKey fields within certain models and forcing those foreign keys to use a proxy-model, instead of its originating model.
Here's some examples to make it clearer:
App_1-model.py
class Recipe(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField()
...
class Ingredient(models.Model):
name = models.CharField(max_length=200)
recipe = models.ForeignKey(Recipe)
weight = models.IntegerField()
App_2-model.py (Imports App_1 models)
class RecipeDisplayProxy(Recipe):
class Meta:
proxy = True
#property
def total_weight(self):
# routine to calculate total weight
return '100g'
class IngredientDisplayProxy(Ingredient):
class Meta:
proxy = True
#property
def weight_lbs(self):
# routine to convert the original weight (grams) to lbs
return '2lb'
App_2.views.py
def display_recipe(request, slug):
recipe = get_object_or_404(RecipeDisplayProxy, slug=slug)
return render(
request,
'display_recipe/recipe.html',
{'recipe': recipe}
)
App_2-template.html
<h2 class="display-4">{{ recipe.name }}</h2>
<p>{{ recipe.total_weight }}</p> <!-- This works fine, as expected //-->
<ul>
{% for recipe_ingredient in recipe.ingredient_set.all %}
<li>{{recipe_ingredient.ingredient}} –
{{recipe_ingredient.weight_lbs}}</li>
<!--
The above line doesn't return anything as the 'Ingredient' model, not the "IngredientDisplayProxy' is being returned. (As expected)
-->
{% endfor %}
</ul>
What's happening here is that I'm successfully returning the RecipeDisplayProxy model as specified in the view, but when I access ingredient_set it returns the Ingredient model, rather than the IngredientDisplayProxy (as expected).
So how do I force ingredient_set to return IngredientDisplayProxy models instead?
I tried implementing the code found here:
Django proxy model and ForeignKey
But had no luck. I then started digging into the init() method for RecipeDisplayProxy - to see if I could overwrite the models used in the ingredient_set, but couldn't find anything that would give me the right response.
So any ideas?
Or, am I just taking this down a bad path - and should be considering a different design altogether?

From the view you are returning the recipe instance, but in the template you are accessing the ingredient through the recipe, but it should be the other way round, from ingredient you can access the recipe.Now for the proxy model, better read this documentation

Looks like I Was doing some things wrong, and so on the advice of fips I went back and did the following:
class RecipeDisplayProxy(Recipe):
class Meta:
proxy = True
#property
def total_weight(self):
# routine to calculate total weight
return '100g'
#property
def ingredient_set(self):
qs = super(RecipeDisplayProxy, self).ingredient_set
qs.model = IngredientDisplayProxy
return qs
It was that simple :'( so thank you for the help and suggestions.

Related

How to access object count in template using model manager?

I have a model which creates Memo objects. I would like to use a custom Model Manager's posted method to return the total number of Memo objects - then use this number within a template. I am trying to keep as much of my code as possible within my Models and Model Managers and less within my Views as I read that this was a best practice in 'Two Scoops of Django'.
In the shell I can get the number of memos as such:
>>> from memos.models import Memo
>>> Memo.objects.all()
<QuerySet [<Memo: Test Memo 2>, <Memo: Test Memo 1>]>
>>> Memo.objects.all().count()
2
This is what my Model and Model Manager look like:
class MemoManager(models.Manager):
use_for_related_fields = True
def posted(self):
return self.count()
class Memo(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_time = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
objects = MemoManager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('memos-detail', kwargs={'pk': self.pk})
I know this is clearly the wrong way to do it but I have confused myself here. So how do I use my Model Manager to get the count of objects and use it in a template like: {{ objects.all.count }}?
P.S. I see other posts that show how to do this within the view but as stated I am trying not to use the view. Is using the view required? I also understand my posted method is written incorrectly.
I'm sorry but you have misinterpreted what was written in TSD. The Lean View Fat Model is meant to keep code which pertains to 'business logic' out of the views, and certain model specific things. A request should be handled by a view. So when you want to load a template, you must first have a GET request to your app.
A view function should be written such that Validation of POST data or the Creation of a new object in DB or Querying/Filtering for GET requests should be handled in the corresponding serializer/model/model manager.
What should be happening while you want to load your template.
Have a url for the template that you have created and a view function mapped for it
In the view function you should render said template and pass the necessary data inside the context.
To keep in line with the Lean View Fat Model style, if you want to get a Queryset of of Memo's but only those which have their is_deleted fields set to False, you can overwrite the model manager get_queryset() method for Memo model.
If you want to create a new Memo with a POST request, you can handle
the creation using a ModelForm!
Hope this clears things up!
EDIT:
How to pass a context to a template, in your case the memo count.
def random_memo_view(request):
context = {'memo_count': Memo.posted()}
return render(request, 'template.html', context=context)
RE-EDIT
I just checked that you were using DetailView. In this case follow this from the django docs.
Class Based Views: Adding Extra Context

Chaining Django models: how to format results on the template according to the specific model

I have a view that allows me to work with two different models at once, thanks to itertools chain. I'm rendering the instances of the two chained models inside a table in my template, and I'd need the rows of the table to be formatted differently in case the instances are from one model as opposed to the other.
So basically: I'm chaining two models and displaying their instances in a table, and all the rows of the table that contain instances from model A should be formatted with a yellow background and all the rows containing instances from model B should have a blue background instead.
This is the view:
class BaseView(generic.ListView):
template_name = 'base/base_list.html'
context_object_name = 'base_list'
def get_queryset(self):
queryset = Document.objects.order_by('due_date')
return queryset
def get_context_data(self, **kwargs):
context = super(BaseView, self).get_context_data(**kwargs)
context['object_list'] = sorted(
itertools.chain(Program.objects.all(), Document.objects.all()),
key=attrgetter('validity_date'),
reverse=True)
return context
In logic, what I'd need in the template would be something like this:
if
object in object_list ***belongs*** to Program.objects.all()
(etc)
else
(etc)
The question is: how should I express that belongs?
I've also looked into template tags but could not find the right way to go.
Thank you in advance.
As I mentioned in the comments, you should look for a way of identifying the model itself rather than checking if it is in a list. There is a built-in way of accessing the model name, but unfortunately that is inside the _meta attribute and you're not allowed to use attributes that start with underscores in a template.
So instead I would recommend simply adding one to your class definitions:
class Program(models.Model):
model_name = 'Program'
...
Now you can just do:
{% if object.model_name == 'Program' %}
...
{% else %}
...
{% endif %}

Django - get data from foreign key

I'm working on a Django project and attempting to create some linked models for my data which In think is working, but I cannot seem to work out how to access the linked data.
class One(models.Model)
name = models.CharField(max_length=50)
list = models.ArrayField(models.CharField(max_length=50), blank=True)
def __str__(self):
return self.name
class Many(models.Model)
name = models.CharField(max_length=50)
related = models.ForeignKey(One, null=True, blank=True)
def __str__(self):
return self.name
This is the general relationship I have set up.
What I am trying to do is, in a template have access to a list of all 'Ones', and via each of those, can access each Many and it's related attributes. I can see how to access the attributes for a single 'One', but not how to pass all of them and their related 'Many' models and the related attributes for each. Essentially the output I'd like would have a drop down list with the One's, and when this is submitted some Javascript will use the list in the 'Many' model to do some stuff.
Any advice would be much appreciated.
If you already have the objects of One model class, you can access the many objects using many_set (refer: backward relations):
{% for one_obj in one_objs %}
{% for m_obj in one_obj.many_set.all %}
# do stuff with m_obj here
{% endfor %}
{% endfor %}
One important thing to note here is that this will execute a db query for each m_obj. To make this efficient, you could prefetch the many_set with one_objs in your view.
In your view, use prefetch_related:
one_objs = One.objects.all().prefetch_related('many_set')
You can use Django's "prefetch_related" and Django's "related_name".
Also, this question has been answered here.
Though, here is what you might want, first, change your foreign key definition to this :
related = models.ForeignKey(One, null=True, blank=True, related_name='relateds')
Then you can reverse-fetch the foreign keys:
one = One.objects.get(name="TheOneYouWant").prefetch_related('relateds')
manys = one.relateds
Reverse lookups are accessible through an object's ___set attribte. So to get all the "Many" objects for a given "One" you could do one.many_set
Django reverse lookup of foreign keys
Regarding usage in a template, sets are accessible by adding "all" (since the set returns a queryset, you can run queries against it, including in the template)
Access ForeignKey set directly in template in Django
See the relevant section of the Django Documentation: https://docs.djangoproject.com/en/dev/topics/db/queries/#following-relationships-backward

Django Incrementing Model Instance Attribute in Views w/ F()+1 and Accessing from Template

So I have a model Listing() that has a field views. In my one of my views, when someone looks at the listing's page, the views field is incremented by one via listing.views = F('views') + 1 and listing.save(update_fields=['views']). My issue is that when I access the views attribute from that same template using {{ listing.views }}, instead of display the current amount of views, the template displays F(views) + Value(1) (literally that text). Now, I assume I could use a Model method such as def get_views() which will return self.views, but I was wondering why I am getting this weird issue. Also, is there a way without writing a model method that I can get the actual integer instead of the odd F(views) + Value(1)?
Here is my current code:
models.py
class Listing(models.Model):
...
views = models.IntegerField(default=0)
listings.py
class ListingView(View):
def get(self, request):
listing_id = request.GET['listing_id']
listing = get_object_or_404(Listing, id=listing_id)
listing.views = F('views') + 1
listing.save(update_fields=['views'])
return render(request, 'listings.html', {'listing': listing})
listings.html
<html>
{{ listing.views }}
</html>
Using F expressions like this requires you to re-fetch the item once saved in order to get updated values (due to the nature of the F expression updating at a database level and not the model instance itself; perhaps that's where the decrease in operational costs come in).
From the docs -
In order to access the new value that has been saved in this way, the object will need to be reloaded:
reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()

Model limit_choices_to={'user': user}

I went to all the documentation, also I went to the IRC channel (BTW a great community) and they told me that is not possible to create a model and limit choices in a field where the 'current user' is in a ForeignKey.
I will try to explain this with an example:
class Project(models.Model):
name = models.CharField(max_length=100)
employees = models.ManyToManyField(Profile, limit_choices_to={'active': '1'})
class TimeWorked(models.Model):
project = models.ForeignKey(Project, limit_choices_to={'user': user})
hours = models.PositiveIntegerField()
Of course that code doesn't work because there is no 'user' object, but that was my idea and I was trying to send the object 'user' to the model to just limit the choices where the current user has projects, I don't want to see projects where I'm not in.
Thank you very much if you can help me or give me any advice, I don't want to you write all the app, just a tip how to deal with that. I have 2 days with this in my head and I can't figure it out :(
UPDATE: The solution is here: http://collingrady.wordpress.com/2008/07/24/useful-form-tricks-in-django/ sending request.user to a model.
This limiting of choices to current user is a kind of validation that needs to happen dynamically in the request cycle, not in the static Model definition.
In other words: at the point where you are creating an instance of this model you will be in a View and at that point you will have access to the current user and can limit the choices.
Then you just need a custom ModelForm to pass in the request.user to, see the example here:
http://collingrady.wordpress.com/2008/07/24/useful-form-tricks-in-django/
from datetime import datetime, timedelta
from django import forms
from mysite.models import Project, TimeWorked
class TimeWorkedForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
self.fields['project'].queryset = Project.objects.filter(user=user)
class Meta:
model = TimeWorked
then in your view:
def time_worked(request):
form = TimeWorkedForm(request.user, request.POST or None)
if form.is_valid():
obj = form.save()
# redirect somewhere
return render_to_response('time_worked.html', {'form': form})
Model itself doesn't know anything about current user but you can give this user in a view to the form which operates models objects (and in form reset choices for necessary field).
If you need this on admin site - you can try raw_id_admin along with django-granular-permissions (http://code.google.com/p/django-granular-permissions/ but I couldn't rapidly get it working on my django but it seems to be fresh enough for 1.0 so...).
At last, if you heavily need a selectbox in admin - then you'll need to hack django.contrib.admin itself.
Using class-based generic Views in Django 1.8.x / Python 2.7.x, here is what my colleagues and I came up with:
In models.py:
# ...
class Proposal(models.Model):
# ...
# Soft foreign key reference to customer
customer_id = models.PositiveIntegerField()
# ...
In forms.py:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.forms import ModelForm, ChoiceField, Select
from django import forms
from django.forms.utils import ErrorList
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from .models import Proposal
from account.models import User
from customers.models import customer
def get_customers_by_user(curUser=None):
customerSet = None
# Users with userType '1' or '2' are superusers; they should be able to see
# all the customers regardless. Users with userType '3' or '4' are limited
# users; they should only be able to see the customers associated with them
# in the customized user admin.
#
# (I know, that's probably a terrible system, but it's one that I
# inherited, and am keeping for now.)
if curUser and (curUser.userType in ['1', '2']):
customerSet = customer.objects.all().order_by('company_name')
elif curUser:
customerSet = curUser.customers.all().order_by('company_name')
else:
customerSet = customer.objects.all().order_by('company_name')
return customerSet
def get_customer_choices(customerSet):
retVal = []
for customer in customerSet:
retVal.append((customer.customer_number, '%d: %s' % (customer.customer_number, customer.company_name)))
return tuple(retVal)
class CustomerFilterTestForm(ModelForm):
class Meta:
model = Proposal
fields = ['customer_id']
def __init__(self, user=None, *args, **kwargs):
super(CustomerFilterTestForm, self).__init__(*args, **kwargs)
self.fields['customer_id'].widget = Select(choices=get_customer_choices(get_customers_by_user(user)))
# ...
In views.py:
# ...
class CustomerFilterTestView(generic.UpdateView):
model = Proposal
form_class = CustomerFilterTestForm
template_name = 'proposals/customer_filter_test.html'
context_object_name = 'my_context'
success_url = "/proposals/"
def get_form_kwargs(self):
kwargs = super(CustomerFilterTestView, self).get_form_kwargs()
kwargs.update({
'user': self.request.user,
})
return kwargs
In templates/proposals/customer_filter_test.html:
{% extends "base/base.html" %}
{% block title_block %}
<title>Customer Filter Test</title>
{% endblock title_block %}
{% block header_add %}
<style>
label {
min-width: 300px;
}
</style>
{% endblock header_add %}
{% block content_body %}
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Save" class="btn btn-default" />
</form>
{% endblock content_body %}
I'm not sure that I fully understand exactly what you want to do, but I think that there's a good chance that you'll get at least part the way there using a custom Manager. In particular, don't try to define your models with restrictions to the current user, but create a manager that only returns objects that match the current user.
Hmmm, I don't fully understand your question. But if you can't do it when you declare the model maybe you can achieve the same thing with overriding methods of the class of objects where you "send" the user object, maybe start with the constructor.
Use threadlocals if you want to get current user that edits this model. Threadlocals middleware puts current user into process-wide variable. Take this middleware
from threading import local
_thread_locals = local()
def get_current_user():
return getattr(getattr(_thread_locals, 'user', None),'id',None)
class ThreadLocals(object):
"""Middleware that gets various objects from the
request object and saves them in thread local storage."""
def process_request(self, request):
_thread_locals.user = getattr(request, 'user', None)
Check the documentation on how to use middleware classes. Then anywhere in code you can call
user = threadlocals.get_current_user

Categories

Resources