This may be a design question.
Question is "What is the best way to find offers that needs to have feedback sent by logged in user". In Feedbacks site there are 3 tabs: "Sent", "Received", "Send feedback".
"Send feedback" tab there's a table with "Offer id","username(buyer/sender" and "Send feedback" link pointing to feedback form.
Here's the code which should help understand what I mean.
Offers are displayed until some user buys it.
Offer is being closed, and new Order (storing order details) instance is created for this offer.
I'm trying to implement a Feedback app, where both sides of offer transaction can
send feedback about transaction.
Let's skip the "ended" or "running" offer problem.
class Offer(models.Model):
"""Offer is displayed for 5 days, then it's being ended by run everyday cron script.
If someone buys the offer end_time is being set, and offer is treated as ended.
Both sides of transaction may send feedback.
"""
whose = models.ForeignKey(User, verbose_name="User who has created the offer")
end_time = models.DateTimeField(blank=True, null=True, help_text="")
field = ()
fields2 = ()
order = models.ForeignKey(Order, balnk=True, null=True, help_text="Order details")
class Order(models.Model):
"""stores order details like user, date, ip etc."""
order_1_field_details = ()
who = models.ForeignKey(User, verbose_name="User who bought the offer")
offer_id = models.PositiveIntegerField("know it's unnecessary")
offer_data = models.TextField('offer data dict here')
class Feedback(models.Model):
offer_id = models.PositiveIntegerField()
sent_by = models.ForeignKey(User, verbose_name="Offer sender")
received_by = models.ForeignKey(User, verbose_name="Offer receiver")
def get_offer(self):
try:
Offer.objects.get(id=self.offer_id)
except Offer.DoesNotExist:
return None # offer moved to archive
In first draft there was a offer = models.ForeignKey(Offer) instead of offer_id field,
but I am going to move some old offers from Offer table to another one for archiving.
I would like the feedback stay even if I 'archive' the offer. In feedback list there will be an 'Offer id" link and for offers older than 60 days user will see "moved to archive" when clicking "details".
All I can think of at the moment is getting offers which hasn't expired, but there was a buyer.
ended() is a manager returning self.filter(end_date__isnull=False)
offers_with_buyer = models.Q(Offer.objects.ended().filter(whose__exact=request.user, order__isnull=False) | models.Q(Offer.objects.ended().filter(order__who__exact=request.user)
How do I check if there's a feedback for these offers ?
I know I should return user and offer id from queryset above and check if they exist in Feedback.offer_id and Feedback.sent_by.. or maybe I should change model design completely ...
First, how you're handling the end date is very contrived. If the offer ends 5 days after it's created, then just set that automatically:
from datetime import datetime, timedelta
class Offer(models.Model):
...
def save(self, *args, **kwargs):
self.end_date = datetime.now() + timedelta(days=5)
super(Offer, self).save(*args, **kwargs)
Then, just modify your ended manager to return instead: self.filter(end_date__lte=datetime.now())
However, I generally prefer to add additional methods to my default manager to deal with the data in various ways:
class OfferQuerySet(models.query.QuerySet):
def live(self):
return self.filter(end_date__gt=datetime.now())
def ended(self):
return self.filter(end_date__lte=datetime.now())
class OfferManager(models.Manager):
use_for_related_fields = True
def get_query_set(self):
return OffersQuerySet(self.model)
def live(self, *args, **kwargs):
return self.get_query_set().live(*args, **kwargs)
def ended(self, *args, **kwargs):
return self.get_query_set().ended(*args, **kwargs)
(Defining a custom QuerySet and then using methods on the Manager that just proxy to the QuerySet is the way the Django core team does it, and allows you to use the methods anywhere in the chain, instead of just the front.)
As far as "archiving" your Offers go, it's extremely bad practice to divvy out similar data into two different models/tables. It's exponentially increases the order of complexity in your app. If you want to "archive" an offer. Add a field such as:
is_archived = models.BooleanField(default=False)
You can then create another method in your Manager and QuerySet subclasses to filter out just archived or live offers:
def live(self):
return self.filter(is_archived=False, end_date__gt=datetime.now())
def archived(self):
return self.filter(is_archived=True)
If you really need another model (such as for a separate view in the admin for just archived offers) you can create a proxy model:
class ArchivedOfferManager(models.Manager):
def get_query_set(self):
return super(ArchivedOfferManager, self).get_query_set().filter(is_archived=True)
def create(self, **kwargs):
kwargs['is_archived'] = True
return super(ArchivedOfferManager, self).create(**kwargs)
class ArchivedOffer(models.Model)
class Meta:
proxy = True
objects = ArchivedOfferManager()
def save(self, *args, **kwargs):
self.is_archived = True
super(ArchivedOffer, self).save(*args, **kwargs)
Related
I am using soft deletes on one of my models in Django, and I am overwriting the default manager to always return active records only, using something like:
class ActiveRecordManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
class Tag(models.Model):
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveRecordManager()
class Photo(models.Model):
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name="photos")
objects = ActiveRecordManager()
All works well. However, when I do:
tag = Tag.objects.get(pk=100)
And then I try to get the associated photos:
photos = tag.photos.all()
Then I get photos that are deleted. I only want to return objects that are not deleted (so my regular objects list. I was reading about _base_mangers in Django, which seems to control this, but the documentation recommends against filtering objects out:
If you override the get_queryset() method and filter out any rows,
Django will return incorrect results. Don’t do that. A manager that
filters results in get_queryset() is not appropriate for use as a base
manager.
But what I am not clear about is how I am supposed to filter these results. Any thoughts?
UPDATE:
I was asked to explain how this question is different from this one:
How to use custom manager with related objects?
In this 8 year old question they mention a deprecated method. That deprecated method is superseded by the method I outline below (base_managers) which according to the documentation I should not use. If people think I should use it, can you please elaborate?
why not use custom query methods instead of overriding manager as it may produce problems for example in admin pages?
class ActiveModelQuerySet(models.QuerySet):
def not_active(self, *args, **kwargs):
return self.filter(is_deleted=True, *args, **kwargs)
def active(self, *args, **kwargs):
return self.filter(is_deleted=False, *args, **kwargs)
class Tag(models.Model):
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveModelQuerySet().as_manager()
class Photo(models.Model):
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name="photos")
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveModelQuerySet().as_manager()
you can then filter your models however you want
tag = Tag.objects.active(pk=100)
deleted_tags = Tag.objects.not_active()
photos = tag.photos.active()
also note that you need is_deleted attribute in all your models that have the soft delete functionality like Photo in your case
I have a model which store an API response. I want to pass a dict which contain response to update a model.
class Currency_Trending_Info(models.Model):
"""Stores a market's last price as well as other stats based on a 24-hour sliding window."""
crypto_currency = models.ForeignKey(Crypto_Currency, on_delete=models.CASCADE)
market = models.CharField(max_length=30, blank=True, null=False)
last_volume = models.DecimalField(max_digits=18, decimal_places=9)
last_volume_to = models.DecimalField(max_digits=18, decimal_places=9)
last_trade_id = models.BigIntegerField()
...
...
but API response format looks like:
{
...
'TYPE':'5',
'MARKET':'CCCAGG',
'FROMSYMBOL':'BTC',
'TOSYMBOL':'USD',
'FLAGS':'4',
'PRICE':6276.14,
'LASTUPDATE':1539279469,
'LASTVOLUME':0.03845327,
'LASTVOLUMETO':239.21394734299997,
'LASTTRADEID':'52275450'
...
}
so my question is is a there way to define alternative name to a field or should I just change model fields to correspond API response e.g. lastvolume = models.DecimalField(max_digits=18, decimal_places=9) (and of course use the key.lower() to turn a key into a lower case string.)
any suggestions are welcome
edit:
saving to the db
entry=Crypto_Currency(crypto_currency="BTC", price=6276, unix_timestamp=1539279469)
entry.save()
# Dictionary with the trending data
currency_info = Currency_Trending_Info(crypto_currency=entry,**dic)
One approach is override model.save method to do custom task while saving object. From there you can manually assign value to each field and save it. Refer http://books.agiliq.com/projects/django-admin-cookbook/en/latest/override_save.html or official doc .
It will look something like this:
class Crypto(models.Model):
...
def save(self, *args, **kwargs):
self.attribte_name = kwargs['value']
super(Model, self).save(*args, **kwargs)
Don't forget to call super. Because db not gonna change if you miss that.
In my Django model I have a many to many connection. I would also like to have the option of selecting a primary diagnosis from the connected diagnoses.
class Case(models.Model):
diagnoses_all_icd_10 = models.ManyToManyField('ICD10')
How can I create a choice field that displays only the associated diagnoses for selection? It is important that the solution also works in the Django admin.
I think through argument works for you.
https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.ManyToManyField.through
In your case:
class Case(models.Model):
diagnoses_all_icd_10 = models.ManyToManyField(ICD10, through='DiagnoseOrder')
class DiagnoseOrder(models.Model):
case = models.ForeignKey(Case, on_delete=models.CASCADE)
icd_10 = models.ForeignKey(ICD10, on_delete=models.CASCADE)
is_primary = models.BooleanField(default=False)
def save(self, *args, **kwargs):
# If not self.is_primary you won't need further query
if self.is_primary:
# Query if there is a primary order related to this case
existing_primary = DiagnoseOrder.objects.filter(is_primary=True, case=self.case).first()
if existing_primary:
# You can change existing primary's status *up to your need
existing_primary.is_primary = False
existing_primary.save()
super(DiagnoseOrder, self).save(*args, **kwargs)
Then, you can use InlineModelAdmin for Django admin customization.
Further reading:
https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#django.contrib.admin.StackedInline
https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#django.contrib.admin.TabularInline
I'm working on a DIY application (Django 1.5) and I've reached a roadblock. The main models involved are Guide, Tool, Item, Step. A Guide can have many Tools and Items, and a Tool or Item can belong to many Guides. The same goes for a Step - it can have many Tools and Items, and a Tool or Item can belong to many Steps. A Guide has many Steps and a Step belongs to a Guide.
Guide many-to-many Items
Guide many-to-many Tools
Guide one-to-many Steps
Step many-to-many Items
Step many-to-many Tools
The roadblock...
At the Guide-level, I want the Tool and Item options to be limitless. But at the Step-level, I want the Tool and Item options to be limited to those assigned to the Guide it belongs to. Basically, when creating/editing a Step, I want to list checkboxes for all the Tools and Items available through the Guide. The user selects those that are needed for the current Step. Each Step will have different combinations of Tools and Items (thus the need for checkboxes).
I discovered the ModelMultipleChoiceField for the Step's ModelForm class. There I can specify a queryset. BUT, how do I gain access to the instance of the Guide model to retrieve its Tools and Items so that I can properly build selections? I would like to provide queries similar to what you would do in a View...
Guide.objects.get(pk=n).tools.all()
Guide.objects.get(pk=n).items.all()
How can I achieve that via ModelMultipleChoiceField? I hope I was able to explained this clearly.
Thanks in advance for any help.
class Tool(models.Model):
name = models.CharField(max_length=100)
...
class Item(models.Model):
name = models.CharField(max_length=100)
...
class Guide(models.Model):
models.CharField(max_length=100)
description = models.CharField(max_length=500)
tools = models.ManyToManyField(Tool, null=True, blank=True)
items = models.ManyToManyField(Item, null=True, blank=True)
...
class Step(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
guide = models.ForeignKey(Guide)
tools = models.ManyToManyField(Tool, null=True, blank=True)
items = models.ManyToManyField(Item, null=True, blank=True)
EDIT: 5/2
After further reading, it looks like I have to override the __init__ method of ModelMultipleChoiceField, where I gain a reference to self.instance, allowing me to create my query like, self.instance.guide.tools.all() and self.instance.guide.items.all(). And then create the fields via fields['field_name'].
I'm at work now so I won't be able to try this out until later tonight. I'll report back my findings.
What I ended up doing is the following. I defined a method in my ModelForm class for creating the ModelMultipleChoiceField. The reason is at the point of requesting the Create Step page, there is no Guide associated with the Step, not until you save (POST...assuming validation is successful). But I do have access to the slug field of the Guide that the Step will be created for. I get if from the URL. And the slug field is unique in my app. So I pass the slug from my view to the form via the new method I created. From there, I'm able to get the tools assigned to the guide and make those options available on the form, in my template.
forms.py
class NewStepForm(forms.ModelForm):
...
def create_tools_field(self, slug):
self.fields['tools'] = forms.ModelMultipleChoiceField(
queryset=Guide.objects.get(slug=slug).tools.all(),
widget=forms.CheckboxSelectMultiple(attrs={'class': 'unstyled'})
)
...
views.py
class NewStepView(View):
form_class = NewStepForm
initial = {'key': 'value'}
template_name = "guides/guide_step_new.html"
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(NewStepView, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
slug = kwargs.get('slug')
form = self.form_class(initial=self.initial)
form.create_tools_field(slug)
return render(request, self.template_name, {'form': form})
class Facilites(models.Model):
id = models.CharField(max_length=32, primary_key=True)
name = models.CharField(max_length=128)
class Objects(models.Model):
name = models.CharField(max_length=64)
facilityid = models.ForeignKey(Facilities)
class Admins(models.Model):
user = models.OneToOneField(User)
facilities = models.ManyToManyField(Facilities)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Admins.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
What i want is to have users (admins) only be able to add or modify "facilityid" in Objects to values specified in their Admins.facilities.
So if some user is named UserA and has facilities = ('FacA', 'FacB'), when he is adding a new object to DB, he shoudln't be able to add something like Object('Random object', 'FacC')
Also, he shouldn't be able to modify existing objects to facilities he doesn't belong to.
I have filtered the Objects with:
def queryset(self, request):
qs = super(ObjectsAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(facitityid__id__in = request.user.get_profile().facilities.all())
so users can only see the object that belong to their facilities. But i have no idea how to prevent them from adding/editing object out of their facilities.
edit:
found the answer here: https://stackoverflow.com/a/3048563/1421572
It turns out that ModelAdmin.formfield_for_foreignkey was the right answer in this situation: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
I would do this with either a pre-made facility list (i.e. You could create an integer field that is hooked to FACILITY_CHOICES for the user to select from.)
If only admins can do it then permissions sounds quite viable. You can also do form validation to check for errors against the db. Depending on how many facilities you have you may want a different approach.
You can do this same technique with a models.CharField as well. So perhaps assign a 3 letter facility code to each facility and require the entry to match one of the 3 letter strings. You could even have the list in a .txt file to read from. There are really so many ways to do this. I will provide an example of a pre-made facility list and accessing the facility a particular user belongs to from the api / template:
NYC_FACILITY = 0
LA_FACILITY = 1
ATL_FACILITY = 2
FACILITY_CHOICES = (
(NYC_FACILITY, 'NYC'),
(LA_FACILITY, 'LA'),
(ATL_FACILITY, 'ATL'),
class Facility(models.Model):
name = models.IntegerField(choices=FACILITY_CHOICES, default="NYC")
class Meta:
order_by = ['name']
verbose_name_plural = "facilities"
verbose_name = "facility"
def __unicode__(self):
return self.name
As far as viewing the facilities page that a particular user belongs to you will have a m2m one to one or FK relationship between the objects. If FK or m2m relationship then you will have access to additional methods of that model type. get_related However, I'm not going to use get_related in my example. Once you are in an instance you then have access to entry_set.
# models.py
from django.auth import User
class Person(User):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
facility_loc = models.ForeignKey('Facility') # ForeignKey used assuming only one person can belong to a facility.
slug = models.SlugField(unique=True)
def get_absolute_url(self):
return "/%s/%s/" % self.facility_loc % self.slug
# views.py - TemplateView is automatically given a context variable called params which parses data from the URL. So, I'll leave the regex in the URLConf up to you.
class UserFacilityView(TemplateView):
model = Facility
template_name = "user_facility.html"
Now in your template you should be able to access facility_set from a User instance or user_set from a facility instance.