How to create a choice field from many to many connection - django

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

Related

How to I automatically filter out is_deleted records in an associated table in Django?

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

Django field choices not properly updating

I have two models, one that loads the other model it's titles in a choice field dynamically. I fixed it so far that if I add a new object to the model which the titles are used from by updating the choice list in the init method, the choice list gets updated immediately. However when I decide to choose it as option and save it I get: Select a valid choice. example is not one of the available choices. When I restart the server it does work, what I did:
model:
class Assessment(models.Model):
title = models.CharField(max_length=200)
SPECIFIC_REQUIREMENTS_CHOICES = ()
SPECIFIC_REQUIREMENTS_CHOICES_LIST = []
for sRequirement in SpecificRequirements.objects.all():
SPECIFIC_REQUIREMENTS_CHOICES_LIST.append((sRequirement.title, sRequirement.title))
SPECIFIC_REQUIREMENTS_CHOICES = SPECIFIC_REQUIREMENTS_CHOICES_LIST
sRequirementChoice = models.CharField(max_length=200, choices=SPECIFIC_REQUIREMENTS_CHOICES,
default='')
forms:
class AssessmentForm(forms.ModelForm):
class Meta:
model = Assessment
fields = ['title', 'sRequirementChoice']
def __init__(self, *args, **kwargs):
super(AssessmentForm, self).__init__(*args, **kwargs)
SPECIFIC_REQUIREMENTS_CHOICES_LIST = []
for sRequirement in SpecificRequirements.objects.all():
SPECIFIC_REQUIREMENTS_CHOICES_LIST.append((sRequirement.title, sRequirement.title))
SPECIFIC_REQUIREMENTS_CHOICES = SPECIFIC_REQUIREMENTS_CHOICES_LIST
self.fields['sRequirementChoice'].choices = SPECIFIC_REQUIREMENTS_CHOICES
That's not how Model choices work. You are not supposed to populate choices dynamically in models.
You should consider using a ForeignKey relation with SpecificRequirements in your model.

django admin add data with fixed value in some field

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.

Django complex query comparing 2 models

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)

Django Admin Drop down selections

I use the django admin for updating various data on the MySQL database. I use the basic django admin for this. When entering in new data, I would like to be able to have it so people can only select from a few options to enter in new text data.
For example:
The table holds colors, so instead of letting the admin person (data entry individual in our case) just enter in anything into the text box, how can I get the django admin to only give them several options to choose from?
This can be done via the model field argument choices
myfield = models.CharField(max_length=256, choices=[('green', 'green'), ('red', 'red')])
The only problem with this is that if you already have a value in the database that doesn't match one of these, django might just default it to one of the choices.
If that's a problem and you want to preserve those values, I might override the admin form and either only supply the ChoiceField on add operations or dynamically add whatever is in the DB as one of the valid choices.
class MyForm(ModelForm):
MY_CHOICES = [('green', 'green'), ('red', 'red')]
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
if self.instance.id:
CHOICES_INCLUDING_DB_VALUE = [(self.instance.field,)*2] + self.MY_CHOICES
self.fields['my_field'] = forms.ChoiceField(
choices=CHOICES_INCLUDING_DB_VALUE)
class MyAdmin(admin.ModelAdmin):
form = MyForm
It's better to define a "models.CharField" field type in your model
your_choices = (
('1', u'yello'),
('2', u'red'),
('3', u'black'),
)
types = models.CharField(max_length=32, choices=your_choices, null=True, blank=True)
Then in "admin.py" you should add this new field in your custom admin class like below.
class YourModelAdmin(admin.ModelAdmin):
fields = ('types', )
admin.site.register(YourModel, YourModelAdmin)