Comparing values from two models within Django DetailView - django

So I'm attempting to create an automated "tick sheet" as a proof of concept for a proposal to automate a process at my workplace.
At work we receive orders from various Suppliers and need to manually tick off the orders that are received against a list of what is expected on a paper sheet each day.
I'm using the generic DetailView in order to have a separate page for each Retailer and have a model representing the Retailer with a ManyToMany relationship with the Suppliers. I also have an Order model to 'simulate' a connection to the actual WMS database (with the intention of modifying it after getting actual db read access if/when this is put into production.)
I need the expected orders (in the Suppliers ManyToMany relationship) to match against those in the 'real' Order model data, and return an answer as to whether it exists in the db (in a way that I can display it on a template).
After a few frantic attempts to solve it myself I'm a little stumped as to how to achieve this within the context of the DetailView, so I fear I am misunderstanding something...
edit: I should have mentioned I only need the 'supplier code' to match but also intend to have the program check for duplicate orders using the 'order reference' once I've figured this out, as without this functionality the whole thing becomes a bit redundant...
My models.py:
from django.db import models
from django.utils import timezone
class Order(models.Model):
''' To simulate connection to main stock db '''
retailer_code = models.CharField(max_length=4)
retailer_name = models.CharField(max_length=100)
supplier_code = models.CharField(max_length=4)
supplier_name = models.CharField(max_length=100)
order_reference = models.CharField(max_length=20)
despatch_date = models.DateTimeField(default=timezone.now)
def __str__(self):
return f"< {self.order_reference}', {self.supplier_name}, {self.retailer_name} >"
# -------------------------------------------------------------------------------------
class Retailer(models.Model):
retailer_code = models.CharField(max_length=4)
retailer_name = models.CharField(max_length=100)
suppliers = models.ManyToManyField('Supplier')
slug = models.SlugField(unique=True, null=True)
def get_supplier_values(self):
return [(suppliers.supplier_code + ' - ' + suppliers.supplier_name) for suppliers in self.suppliers.all()]
def save(self, *args, **kwargs):
self.slug = self.slug or slugify(self.retailer_code)
super().save(*args, **kwargs)
def __str__(self):
return f"< {self.retailer_code} - {self.retailer_name} >"
class Supplier(models.Model):
supplier_code = models.CharField(max_length=4)
supplier_name = models.CharField(max_length=100)
def __str__(self):
return f"< {self.supplier_code}, {self.supplier_name} >"
My views.py:
from django.shortcuts import render
from django.views.generic.list import ListView
from django.views.generic.detail import DetailView
from .models import Retailer, Order
class RetailerListView(ListView):
model = Retailer
context_object_name = 'retailer_list'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Select Retailer'
return context
class RetailerDetailView(DetailView):
model = Retailer
slug_field = 'retailer_code'
slug_url_kwarg = 'retailer_code'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Order Checklist'
context['db_orders'] = Order.objects.filter(retailer_code=self.object.retailer_code)
return context
def do_order_checklist(self):
pass # WIP
Any help would be appreciated...

Probably you can use Exists to to annotate if the order is in DB. For example:
from django.db.models import Exists, OuterRef
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Order Checklist'
squery=self.object.suppliers.filter(supplier_code=OuterRef('supplier_code'))
context['db_orders'] = Order.objects.filter(
retailer_code=self.object.retailer_code
).annotate(
in_db=Exists(squery)
)
return context
Then show in template:
{% for item in db_orders %}
{% if item.in_db %}
// do something
{% else %}
// else
{% endif %}
{% endfor %}

I see, probably the answer you are looking for is this.
as you get the list of supplier_codes in each retailer. instance you already have the list.
retailers_supplier_codes = [1, 2, 3, ...]
matching_orders = Order.objects.filter(supplier_code__in = retailers_supplier_codes)

Related

Django queryset hide value of object

I have the following (simplified) Model:
class Order(models.Model):
is_anonymized = models.BooleanField(default=False)
billing_address = models.ForeignKey('order.BillingAddress', null=True, blank=True)
I want to hide the billing_address for objects where the customer chosen to do so by setting is_anonymized=True.
My best approach so far was to do that in the init:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.is_anonymized:
self.billing_address = None
self.billing_address_id = None
This works fine in the Admin BUT...
anywhere in the code there are select_related-QuerySets for Orders:
queryset = Order._default_manager.select_related('billing_address')
All places where the select_related-querysets are used, the billing_address is accidentally shown.
Elsewhere (like in the admin), it isn't.
How can I ensure to remove the billing_address everywhere for objects with is_anonymized = True?
I thought about overwriting the queryset in the manager but i couldn't overwrite the billing_address field by condition.
Using the getter-setter pattern was not a good solution because it breaks the admin at multiple places (there are many attributes to cloak like billing_address).
To be more precise:
The goal is to only simulate as if the data would be deleted although persisting in the database.
I would like to start by saying that I do not understand why would you want to hide information from the admin of your system. Unless you have a complex work environment where only the DBA have access to such information, I honestly do not see the point.
To answer your question...
To hide information in the admin page, one option is to disable all links and replace the HTML with the edit link when is_anonymized value is False:
(adapted from answer_1 and answer_2)
admin.py:
from django.utils.html import format_html
class OrderAdmin(admin.ModelAdmin):
list_display = ['anonymous_address']
def anonymous_address(self, obj):
if not obj.is_anonymized:
return format_html(u'{}', obj.id, obj.billing_address.address)
else:
return ("%s" % ('anonymous'))
def __init__(self, *args, **kwargs):
super(OrderAdmin, self).__init__(*args, **kwargs)
self.list_display_links = None
admin.site.register(Order, OrderAdmin)
Note that with this solution admin still has access to BillingAddress model, if you registered it in the admin site. In that case it will be also necessary to override that.
On your queries, you can aggregate values with conditional expressions:
views.py:
from core.models import Order
from django.db.models import When, Case
def anonymous_address(request):
orders = Order.objects.annotate(anonymised_address=Case(
When(is_anonymized=True, then=None),
When(is_anonymized=False, then='billing_address'),
)).values('is_anonymized', 'anonymised_address')
context = {'orders': orders}
return render(request, 'anonymous_address.html', context)
anonymous_address.html:
{% block content %}
{% for order in orders %}
Should be anonymous: {{order.is_anonymized}} <br>
Address: {{order.anonymised_address}}
<hr>
{% endfor %}
{% endblock content %}
And, instead of having this long query in every view, it is possible to replace that by a custom manager:
models.py:
class AnonymousOrdersManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(anonymised_address=Case(
When(is_anonymized=True, then=None),
When(is_anonymized=False, then='billing_address'),
)).values('is_anonymized', 'anonymised_address')
class Order(models.Model):
is_anonymized = models.BooleanField(default=False)
billing_address = models.ForeignKey(BillingAdress, null=True, blank=True, on_delete=models.CASCADE)
objects = models.Manager()
anonymous_orders = AnonymousOrdersManager()
views.py:
def anonymous_address(request):
orders = Order.anonymous_orders.all()
context = {'orders': orders}
return render(request, 'anonymous_address.html', context)

error with slug in django - template it show all posts data in each post

Error with a slug in Django - template it shows all posts data in each post
when I create a new post and write my data it shows all data from other posts why is that?
and how I can fix it?
also how I can add an auto-generation slug?
models.py :
from django.urls import reverse
from django.utils.text import slugify
class Android(models.Model):
title = models.CharField(max_length=50,default="",help_text="this is title for slug not post!")
name = models.CharField(max_length=50,default="")
app_contect = models.CharField(max_length=240,default="")
app_image = models.ImageField(upload_to='images/',null=True, blank=True)
post_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
post_tag = models.CharField(max_length=50,default="",choices = BLOG_SECTION_CHOICES)
slug = models.SlugField(null=True,uniqe=True) # new
def get_absolute_url(self):
return reverse('android_posts', kwargs={'slug': self.slug}) # new
def get_image(self):
if self.app_image and hasattr(self.app_image, 'url'):
return self.app_image.url
else:
return '/path/to/default/image'
def __str__(self):
return self.name
class Meta:
ordering = ('-post_date',)
views.py :
def android_posts(request,slug):
android_posts = Android.objects.all()
context = {'android_posts':android_posts}
return render(request,'android/android_problems_fix.html', { 'android_posts': android_posts })
html page :
{% for android in android_posts %}
<h1 id="font_control_for_header_in_all_pages">{{android.name}}</h1>
<hr>
<p id="font_control_for_all_pages">{{android.app_contect}}</p>
{% endfor %}
url :
path('Android/<slug:slug>', views.android_posts, name='android_posts'),
To autogenerate your slug (and only do it on initial save, so that it remains consistent), add the generation to your model save method:
def save(self, *args, **kwargs):
super(<Model>, self).save(*args, **kwargs)
if not self.pk:
self.slug = <slugify code here>
As for your view/Template, you are specifically selecting all posts using:
android_posts = Android.objects.all()
Passing them to the template, then looping over them with the for loop to display them all.
Instead of this, select only a single object with:
android_post = Android.object.get(pk=<pk value>)
Edit after you added your urls.py:
You can get the unique object for a slug with:
android_post = get_object_or_404(Android, slug=slug)
The use of get_object_or_404 will also handle the case where that record doesn't exist.
You haven't posted your urls.py, so not sure how you're linking to this view, but if it includes the slug in the url, you will be able to get this in the view. My guess is you're not accessing via slug in the url, but via the id field.
Personally, when I slugify some text, I always include the id - it is a better way of ensuring uniqueness. By specifying unique=True on your non-pk slug field, you are likely restricting the titles people can use (2 people couldn't use the same title then!)
To give you an example, this is how I am doing it on one of my models:
def save(self, *args, **kwargs):
if not self.id or not self.slug:
super(Android, self).save(*args, **kwargs)
self.slug = slugify(f"{self.title} {str(self.id)}")
super(Android, self).save(*args, **kwargs)
This slug will always be unique because it includes id - and 2 people could have a record with the same title value without the system objecting.

Django-Filter over Managers

I've been stuck in this for several weeks now and I believe the answer is super simple but somehow I can't find it anywhere online. Which makes me think I'm going about it totally wrong.
All I want to do is be able to filter my stats such as the get_largest_winning_trade function based on the django-filter package. Where am I going wrong? As a side note get_largest_winning_trade is showing the largest winning trade in the Trade Model but it is not being filtered for my criteria. Such as "user".
managers.py
from django.db import models
class TradeQuerySet(models.QuerySet):
def get_users_trades(self, username):
return self.filter(user__username=username)
class TradeManager(models.Manager):
def get_queryset(self):
return TradeQuerySet(self.model, using=self._db)
def get_users_trades(self, username):
return self.get_queryset().get_users_trades(username)
def get_largest_winning_trade(self):
return max([t.profit_loss_value_fees for t in self.all()])
views.py
class StatsView1(LoginRequiredMixin, ListView):
model = Trade
template_name = 'dashboard/stats1.html'
def get_context_data(self, **kwargs):
filter = StatsFilter1(self.request.GET, queryset=self.get_queryset())
context = super().get_context_data(**kwargs)
context['filter'] = filter
context['get_largest_winning_trade'] = Trade.objects.get_largest_winning_trade
return context
stats.html (testing)
filter.qs.get_largest_winning_trade: {{ filter.qs.get_largest_winning_trade }} <br>
Trade.get_largest_winning_trade: {{ Trade.get_largest_winning_trade }} <br>
trade.get_largest_winning_trade: {{ trade.get_largest_winning_trade }} <br>
get_largest_winning_trade: {{ get_largest_winning_trade }} <br> # works but not with filter
Additional Requested Information
Shared the class, it's quite long so I tried to reduce it to what is most helpful. Please let me know if there's anything else.
models.py
class Trade(models.Model):
class Meta:
verbose_name = "Trade"
verbose_name_plural = "Trades"
...
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
status = models.CharField(max_length=2, choices=STATUS_CHOICES, default='cl')
type = models.CharField(max_length=5, choices=TYPE_CHOICES, default=LONG)
broker = models.ForeignKey(Broker, on_delete=models.CASCADE, blank=True, null=True)
asset = models.ForeignKey(Asset, default=DEFAULT_ASSET_ID, on_delete=models.CASCADE, null=True)
#AUTOMATED FIELDS
profit_loss_value_fees = models.FloatField(null=True)
objects = TradeManager()
...
def save(self):
...
self.profit_loss_value_fees = self.get_profit_loss_value_fees()
return super(Trade, self).save()
...
Probably you can do something like this using qs property:
def get_context_data(self, **kwargs):
filter = StatsFilter1(self.request.GET, queryset=self.get_queryset())
context = super().get_context_data(**kwargs)
context['filter'] = filter
context['get_largest_winning_trade'] = filter.qs.get_largest_winning_trade
return context
Update
I think rather than doing it manager, you can do the calculation here with aggregation. Like this:
from django.db.models import Max
...
context['get_largest_winning_trade'] = filter.qs.aggregate(max_value=Max('profit_loss_value_fees'))['max_value']
Reason for using aggregation is to reduce DB hits, because your manager method will hit database multiple times during loop iteration.

Foreign key lookups - slug URL with Django generic list view

I have been searching here and reading the documentation and experimenting in python, but I can't find a solution to my particular mess. I did the Django tutorial, but I'm still confused as to how to pass stuff thru the URL in django when starting to use foreign keys, I'm sure it's pretty simple. I'm a new django user and this is my first post. I have the models Playlist, and PlaylistEntry with mixed relationships to user and videos (not posted). I'm trying to show a detail view that uses a slug of a playlist title, to pull out entries in the playlist. Eg. In python, I can do
entries = PlaylistEntry.objects.filter(playlist__slug='django')
which returns all my entries in the playlist 'Django' correctly.
Here are my models...
class Playlist(models.Model):
class Meta:
verbose_name_plural = 'playliztz'
title = models.CharField(blank=True,
help_text=u'Title of playlist',
max_length=64,
)
user = models.ForeignKey('userprofile.UserProfile',
blank=False,
help_text=u'owns this playlist',
null=False, )
slug = models.SlugField(u'slug',
max_length=160,
blank=True,
editable=False
)
created = models.DateTimeField(editable=False)
modified = models.DateTimeField(editable=False)
def __unicode__(self):
return self.title
def get_absolute_url(self):
return reverse('playlist-detail', kwargs={'pk': self.pk})
def save(self):
self.slug = slugify(self.title)
if not self.id:
self.created = datetime.datetime.today()
self.modified = datetime.datetime.today()
super(Playlist,self).save()
class PlaylistEntry(models.Model):
class Meta:
verbose_name_plural = "playlist entries"
video = models.ForeignKey('video.Video',
blank=True,
default=None,
help_text=u'the video title',
null=True, )
playlist = models.ForeignKey('Playlist',
blank=True,
default=None,
help_text=u'Belongs to this playlist',
null=True,)
def __unicode__(self):
return self.video.video_title
My URLS looks like this...
url(r'^playlist/$', PlaylistList.as_view(), name='user_playlists'),
url(r'^playlist/(?P<slug>[0-9A-Za-z-_.//]+)/$', PlaylistDetail.as_view(), name='playlist_entries'),
And my Views.py looks like this...
class PlaylistList(LoggedInMixin, ListView): # shows a list of playlists
template_name = 'userprofile/playlist_list.html'
model = Playlist
context_object_name = 'playlist_list'
def get_queryset(self):
"""Return currently users playlists."""
return Playlist.objects.filter(user__user=self.request.user)
def get_context_data(self, **kwargs):
context = super(PlaylistList, self).get_context_data(**kwargs)
if not self.get_queryset():
context['error'] = "You don't have any playlists yet."
return context
else:
return context
class PlaylistDetail(LoggedInMixin, DetailView):
model = PlaylistEntry
template_name = 'userprofile/playlist_detail.html'
def get_queryset(self):
self.current_playlist = get_object_or_404(Playlist, slug=self.kwargs['slug'])
# CHECK - Prints out correct entries for playlist in slugfield (current_playlist)
self.entries = PlaylistEntry.objects.filter(playlist__title=self.current_playlist.title)
print self.entries
# Should expect to return the same queryset?
return PlaylistEntry.objects.filter(playlist__title=self.current_playlist.title)
def get_context_data(self, **kwargs):
context = super(PlaylistEntry, self).get_context_data(**kwargs)
context['entries'] = PlaylistEntry.objects.all()
return context
self.entries prints the correct entries for this playlist in the Check bit.
In my playlist template I am using a link sending the playlist.slug - the url looks correct like this /user/playlist/this-particular-playlist-slug.
the error is...
Cannot resolve keyword u'slug' into field. Choices are: id, playlist, video
You've made things much more complicated than they need to be.
The model for your detail view should still be Playlist, not PlaylistEntry. The reason you're getting that error is that the slug is on the Playlist model, but you've told the view to filter on PlaylistEntry.
What you actually want to to is to pass the single Playlist identified by the slug into the template. From there, you can easily iterate through the detail objects associated with that playlist via the reverse relation.
So, change that model setting, and drop both get_context_data and get_queryset from the detail view: you don't need them. Then in the template you can simply do:
{% for entry in playlist.playlistentry_set.all %}

DetailView with year and pk in url

Im trying to make a url setup that would display a detail page based on just a year (not month and day), and a pk. like this /YEAR/ISSUE/
This is what I have tried:
My model.py:
class Mag(models.Model):
name = models.CharField(max_length=500)
issue = models.PositiveIntegerField()
pub_date = models.DateTimeField()
unique_together = ("pub_date", "issue")
def __unicode__(self):
return self.name
#models.permalink
def get_absolute_url(self):
creation_date = timezone.localtime(self.pub_date)
return ('mag_detail', (), {
'year': creation_date.strftime('%Y'),
'pk': self.issue})
My views.py:
class MagDetail(DateDetailView):
model = Mag
pk_url_kwarg='pk'
date_field='pub_date'
My urls.py
urlpatterns = patterns('',
url(r'^(?P<year>\d{4})/(?P<pk>\d+)/$', MagDetail.as_view(), name='mag_detail'),
)
But when I try a url like 2014/1, I get an error that month is not specified.
Is it possible to do what I want with DateDetailView, or do I need to look another class?
What if you use a standard DetailView and override the get_object() method, like this:
from django.shortcuts import get_object_or_404
class MagDetail(DetailView):
model = Mag
def get_object(self):
obj = get_object_or_404(
self.model,
pk=self.kwargs['pk'],
pub_date__year=self.kwargs['year'])
return obj
The DateDetailView expects the format of year/month/day
As a general approach, if you will be using this kind of view in your project for other models than Mag alone, you can derive from it though and change the get_object method, roughly like that:
class YearDetailView(DateDetailView):
def get_object(self, queryset=None):
"""
Get the object this request displays.
"""
year = self.get_year()
# Use a custom queryset if provided
qs = queryset or self.get_queryset()
if not self.get_allow_future() and int(year) > datetime.today().year:
raise Http404(_("Future %(verbose_name_plural)s not available because
%(class_name)s.allow_future is False.") % {
'verbose_name_plural': qs.model._meta.verbose_name_plural,
'class_name': self.__class__.__name__,
})
lookup_args = {"%s__year" % self.get_date_field(): year}
qs = qs.filter(**lookup_args)
return super(BaseDetailView, self).get_object(queryset=qs)
class MagDetail(YearDetailView): # note that we derive from our new class!
model = Mag
pk_url_kwarg='pk'
date_field='pub_date'