Django Retrieve id and name based on a ForeignKey - django

I have the following models :
class Projects(models.Model):
id = models.IntegerField(primary_key=True, blank=True)
name = models.CharField(max_length=20, unique=True)
company = models.CharField(max_length=20, blank=True, null=True)
creation_date = models.DateField(auto_now_add=True, auto_now=False)
class Packages(models.Model):
id = models.IntegerField(primary_key=True, blank=True)
name = models.CharField(max_length=50)
extension = models.CharField(max_length=3)
gen_date = models.DateTimeField(auto_now_add=True, auto_now=False)
project = models.ForeignKey(Projects)
In my views, for the Homepage function, I'm trying to display the last package, AND the associated project. I don't understand how to retrieve the 'project' field (FK) :
try:
lastpackages = Packages.objects.reverse()[:1].get()
except Packages.DoesNotExist:
lastpackages = None
projectid = lastpackages.select_related('project_id')
project = Projects.objects.get(id=lastpackages.project)
return render(request, 'homepage.html', {'lastpackages': lastpackages,
'project': project})
In fact, I want to display the 'projectname' corresponding to the package retrieved by reverse. But the lines projectid and project are not correct. I hope it's enough clear..

Sorry to say but your code is a bit messy. You don't need to look up the Project separately, django ORM does it for you:
package = Package.objects.order_by('-id')[0]
project = package.project
package.project would give you the project associated with the package, no need to query using id.
Some advises here:
You don't need to define id, django will do it for you.
Don't use plural form in your model name, django will do it for you.
In view it's usually good exercise to use get_object_or_404 to get the object, it saves your try except block.
reverse() should be used along with order_by() statement. In your case it's easier to just use id to find the last entry, because in django id is auto incremented.

Try this:
lastpackage = Packages.objects.reverse()[0]
project = lastpackage.project

The first thing you need to keep in mind is that lastpackages is a Packages object, not a Queryset, so this line is wrong:
projectid = lastpackages.select_related('project_id')
It should return AttributeError: 'Packages' object has no attribute 'select_related'
About what you're asking, once you have a Packages object, you can get the corresponding project id like this:
lastpackages.project.pk
And the full Projects object, if needed:
lastpackages.project

Related

how to build query with several manyTomany relationships - Django

I really don't understand all the ways to build the right query.
I have the following models in the code i'm working on. I can't change models.
models/FollowUp:
class FollowUp(BaseModel):
name = models.CharField(max_length=256)
questions = models.ManyToManyField(Question, blank=True, )
models/Survey:
class Survey(BaseModel):
name = models.CharField(max_length=256)
followup = models.ManyToManyField(
FollowUp, blank=True, help_text='questionnaires')
user = models.ManyToManyField(User, blank=True, through='SurveyStatus')
models/SurveyStatus:
class SurveyStatus(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
survey_status = models.CharField(max_length=10,
blank=True,
null=True,
choices=STATUS_SURVEY_CHOICES,
)
models/UserSurvey:
class UserSurvey(BaseModel):
user = models.ForeignKey(User, null=True, blank=True,
on_delete=models.DO_NOTHING)
followups = models.ManyToManyField(FollowUp, blank=True)
surveys = models.ManyToManyField(Survey, blank=True)
questions = models.ManyToManyField(Question, blank=True)
#classmethod
def create(cls, user_id):
user = User.objects.filter(pk=user_id).first()
cu_quest = cls(user=user)
cu_quest.save()
cu_quest._get_all_active_surveys
cu_quest._get_all_followups()
cu_quest._get_all_questions()
return cu_quest
def _get_all_questions(self):
[[self.questions.add(ques) for ques in qstnr.questions.all()]
for qstnr in self.followups.all()]
return
def _get_all_followups(self):
queryset = FollowUp.objects.filter(survey__user=self.user).filter(survey__user__surveystatus_survey_status='active')
# queryset = self._get_all_active_surveys()
[self.followups.add(quest) for quest in queryset]
return
#property
def _get_all_active_surveys(self):
queryset = Survey.objects.filter(user=self.user,
surveystatus__survey_status='active')
[self.surveys.add(quest) for quest in queryset]
return
Now my questions:
my view sends to the create of the UserSurvey model in order to create a questionary.
I need to get all the questions of the followup of the surveys with a survey_status = 'active' for the user (the one who clicks on a button)...
I tried several things:
I wrote the _get_all_active_surveys() function and there I get all the surveys that are with a survey_status = 'active' and then the _get_all_followups() function needs to call it to use the result to build its own one. I have an issue telling me that
a list is not a callable object.
I tried to write directly the right query in _get_all_followups() with
queryset = FollowUp.objects.filter(survey__user=self.user).filter(survey__user__surveystatus_survey_status='active')
but I don't succeed to manage all the M2M relationships. I wrote the query above but issue also
Related Field got invalid lookup: surveystatus_survey_status
i read that a related_name can help to build reverse query but i don't understand why?
it's the first time i see return empty and what it needs to return above. Why this notation?
If you have clear explanations (more than the doc) I will very appreciate.
thanks
Quite a few things to answer here, I've put them into a list:
Your _get_all_active_surveys has the #property decorator but neither of the other two methods do? It isn't actually a property so I would remove it.
You are using a list comprehension to add your queryset objects to the m2m field, this is unnecessary as you don't actually want a list object and can be rewritten as e.g. self.surveys.add(*queryset)
You can comma-separate filter expressions as .filter(expression1, expression2) rather than .filter(expression1).filter(expression2).
You are missing an underscore in surveystatus_survey_status it should be surveystatus__survey_status.
Related name is just another way of reverse-accessing relationships, it doesn't actually change how the relationship exists - by default Django will do something like ModelA.modelb_set.all() - you can do reverse_name="my_model_bs" and then ModelA.my_model_bs.all()

Django : the database needs something to populate existing row

models . py :
class Equipe(models.Model):
NomEquipe = models.CharField(max_length=10,unique=True)
Description = models.CharField(max_length=10,unique=True)
class planning (models.Model):
datedebut= models.DateField
datefin=models.DateField
nbrH=models.TimeField
class Conseiller(models.Model):
Matricule = models.CharField(max_length=10,unique=True)
Nom = models.CharField(max_length=200)
Prenom = models.CharField(max_length=200)
Tel = models.CharField(max_length=200)
Mdp = models.CharField(max_length=200)
Email = models.EmailField(max_length=200)
File = models.CharField(max_length=200)
Preavis = models.BooleanField(default=False)
sup = models.CharField(max_length=200)
Equipe = models.ForeignKey(Equipe, on_delete=models.CASCADE)
planning = models.ForeignKey(planning , on_delete = models.CASCADE)
WHEN I try to execute Manage.py makemigrations a have the errors and i need to fix this
I think that you are new in Django. In first place, welcome :D
Second, and if you allow me, I give you some proposals before answer your ask.
Read Pep8 - Pep8 is a code styling by python. You code needs corrections in this way. The names of classes starts with Upper letter. The names of attributes, in lower case.
Be more specific in your ask. In your comment, you are more specific because you write your error...
Your error, is not an error :D. When you define an attribute, as Null=False (default, the attribute is not nulleable), you need specify default value if the table is already created. If you don't define default value, makemigrations command ask one. So, you have two options, define in model or in makemigrations. If your app is some for testing/dev/dummy and your db is clear, put on makemigrations a dummy value... When makemigrations give the two options, select 1 then, press 1 (in you case, attr planning is a foreign key, and is referenced with id integer number) if the attr is Charfield you can put '-', etc. If you have a prod app, you need see if your attribute can be 'nulleable' and set null=True, or see What is the best value in default=? param
Good luck!
The reason : you are trying to add a new field and your (model) database isn't empty ..
Solution : we need to add default value to records that already exists
Ex :
email = models.EmailField()
modify to
email = models.EmailField(default = None)

Python/Django recursion issue with saved querysets

I have user profiles that are each assigned a manager. I thought using recursion would be a good way to query every employee at every level under a particular manager. The goal is, if the CEO were to sign in, he should be able to query everyone at the company - but If I sign on I can only see people in my immediate team and the people below them, etc. until you get to the low level employees.
However when I run the following:
def team_training_list(request):
# pulls all training documents from training document model
user = request.user
manager_direct_team = Profile.objects.filter(manager=user)
query = Profile.objects.filter(first_name='fake')
trickle_team = manager_loop(manager_direct_team, query)
# manager_trickle_team = manager_direct_team | trickle_team
print(trickle_team)
def manager_loop(list, query):
for member in list:
user_instance = User.objects.get(username=member)
has_team = Profile.objects.filter(manager=user_instance)
if has_team:
query = query | has_team
manager_loop(has_team, query)
else:
continue
return query
It only returns the last query that was run instead of the compiled queryset that I am trying to grow. I've tried placing 'return' before 'manager_loop(has_team, query) in order save the values but it also kills the loop at the first non-manager employee instead of continuing to the next employee.
I'm new to django so if there is an better way than recursion to pull the information that I need, I'd appreciate suggestions on that too.
EDIT:
As requested, here is the profile model.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, blank=False)
last_name = models.CharField(max_length=30, blank=False)
email = models.EmailField( blank=True, help_text='Optional',)
receive_email_notifications = models.BooleanField(default=False)
mobile_number = models.CharField(
max_length=15,
blank=True,
help_text='Optional'
)
carrier_options = (
(None, ''),
('#txt.att.net', 'AT&T'),
('#messaging.sprintpcs.com', 'Sprint'),
('#tmomail.net', 'T-Mobile'),
('#vtext.com', 'Verizon'),
)
mobile_carrier = models.CharField(max_length=25, choices=carrier_options, blank=True,
help_text='Optional')
receive_sms_notifications = models.BooleanField(default=False)
job_title = models.ForeignKey(JobTitle, unique=False, null=True)
manager = models.ForeignKey(User, unique=False, blank=True, related_name='+', null=True)
Ok, so it's a hierarchical model.
The problem with your current approach is this line:
query = query | has_team
This reassigns the local name query to a new queryset, but does not reassign the name in the caller. (Well, that's what I think it's trying to do - I am a little rusty but I don't think you can just | together querysets like that.) You'd also need something like:
query = manager_loop(has_team, query)
to propagate the changes via the returned object.
That said, while Django doesn't have built-in support for recursive queries, there are some third party packages that do. Old answers eg (Django self-recursive foreignkey filter query for all childs and Creating efficient database queries for hierarchical models (django)) recommend django-mptt. Your tag mentions postgres, so this post might be relevant:
https://two-wrongs.com/fast-sql-for-inheritance-in-a-django-hierarchy
If you don't use a third-party approach, it should be possible to clean up the evolution of the queryset - cast it to a set and use update or something, since you're accumulating profiles. But the key error is not using the returned modified object.

Django user audit

I would like to create a view with a table that lists all changes (created/modified) that a user has made on/for any object.
The Django Admin site has similar functionality but this only works for objects created/altered in the admin.
All my models have, in addition to their specific fields, following general fields, that should be used for this purpose:
created_by = models.ForeignKey(User, verbose_name='Created by', related_name='%(class)s_created_items',)
modified_by = models.ForeignKey(User, verbose_name='Updated by', related_name='%(class)s_modified_items', null=True)
created = CreationDateTimeField(_('created'))
modified = ModificationDateTimeField(_('modified'))
I tried playing around with:
u = User.objects.get(pk=1)
u.myobject1_created_items.all()
u.myobject1_modified_items.all()
u.myobject2_created_items.all()
u.myobject2_modified_items.all()
... # repeat for >20 models
...and then grouping them together with itertool's chain(). But the result is not a QuerySet which makes it kind of non-Django and more difficult to handle.
I realize there are packages available that will do this for me, but is it possible to achieve what I want using the above models, without using external packages? The required fields (created_by/modified_by and their timefields) are in my database already anyway.
Any idea on the best way to handle this?
Django admin uses generic foreign keys to handle your case so you should probably do something like that. Let's take a look at how django admn does it (https://github.com/django/django/blob/master/django/contrib/admin/models.py):
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.TextField(_('object id'), blank=True, null=True)
object_repr = models.CharField(_('object repr'), max_length=200)
action_flag = models.PositiveSmallIntegerField(_('action flag'))
change_message = models.TextField(_('change message'), blank=True)
So, you can add an additional model (LogEntry) that will hold a ForeignKey to the user that changed (added / modified) the object and a GenericForeignKey (https://docs.djangoproject.com/en/1.7/ref/contrib/contenttypes/#generic-relations) to the object that was modified.
Then, you can modify your views to add LogEntry objects when objects are modified. When you want to display all changes by a User, just do something like:
user = User.objects.get(pk=1)
changes = LogEntry.objects.filter(user=user)
# Now you can use changes for your requirement!
I've written a nice blog post about that (auditing objects in django) which could be useful: http://spapas.github.io/2015/01/21/django-model-auditing/#adding-simple-auditing-functionality-ourselves

Django: pull list of records by state using zip code

I have a Django app that has a series of zip code tagged posts. I'd like to create a page that shows all posts by state but am not sure how to go about it. I do have a ZipCode table, but my Post.zipcode field is not related to it (mostly because it is user entered, and allows zips that are not in the DB or from outside the US).
My relevant models:
class Post(models.Model):
body = models.TextField()
zipcode = models.CharField(max_length=5)
class ZipCode(models.Model):
zipcode = models.CharField(max_length=5)
city = models.CharField(max_length=64)
statecode = models.CharField(max_length=2)
statename = models.CharField(max_length=32)
latitude = models.FloatField()
longitude = models.FloatField()
In my Django view I'd love to take the "state" parameter that is passed in from my url pattern and do something like this:
def posts_by_state(request, state):
posts = Post.objects.filter(zipcode__statecode=state)
...
Unfortunately, my Post.zipcode field is not a foreign key to ZipCode so I get this error if I try:
FieldError at /post/state/VT/
Join on field 'zipcode' not permitted.
Anyone have a hint as to how I should construct a queryset that pulls all posts together for a requested state? Thank you in advance.
I'd suggest updating Post.zipcode to be a ForeignKey to ZipCode. If you can't you could do the lookup like this:
zipcodes = [zip_code.zipcode for zip_code in ZipCode.objects.filter(statecode=state)]
posts = Post.objects.filter(zipcode__in=zipcodes)
On a side note, ZipCode doesn't seem like the right name for that model. Perhaps Location would be better.
Fairly easy solution in the end. What I did was add a new foreign key field to Post called location so Post now looks like this:
class Post(models.Model):
body = models.TextField()
zipcode = models.CharField(max_length=5)
location = models.ForeignKey(ZipCode, null=True, blank=True, default=None)
When I create new Posts, I check to see if the inputted zip string matches a record in the ZipCode database, and if it does I create the location FK. This then allows me to do this in my view:
def posts_by_state(request, state):
posts = Post.objects.filter(location__statecode=state)
...
Thank you Seth and sdolan for your help!