This is a follow-up question from this post. For the sake of completeness and self-containment, I will include all necessary bits in this question.
Consider a very simple example:
# Models
class Cart(models.Model):
name = models.CharField(max_length=20)
class Item(models.Model):
name = models.CharField(max_length=20)
cart = models.ForeignKey(Cart, on_delete=models.CASCADE)
# Execution
class Test(TestCase):
#classmethod
def setUpTestData(cls):
# Save a new cart
c = Cart(name="MyCart")
c.save()
# Save two items to the cart
t = Item(name="item1", cart=c)
t.save()
Item.objects.create(name="item2", cart=c)
def simpleTest(self):
i = Item.objects.get(id=2) # <--- breakpoint
# Do something, but not important for this question
I am using PyCharm to try to understand the inner workings of Django. If we put a breakpoint at the marked line and step into the method call get(). After we get a Manager from __get__ in ManagerDescriptor (due to .objects), the execution now goes to this class method in BaseManager:
#classmethod
def _get_queryset_methods(cls, queryset_class):
def create_method(name, method):
def manager_method(self, *args, **kwargs):
return getattr(self.get_queryset(), name)(*args, **kwargs) # <--- Starts here
manager_method.__name__ = method.__name__
manager_method.__doc__ = method.__doc__
return manager_method
The execution stops at the marked line above. If we step into it, get_queryset() is first called, which returns a new QuerySet object, as expected. Note that the new QuerySet object initializes self._result_cache to None. This attribute caches the result of the QuerySet. After the object is initialized, the get() method is finally called. However, if we inspect the calling QuerySet object (which is the object that is just initialized), its _result_cache has already been populated with the two Item objects that I have saved. I suspect something is probably happening in a different thread, perhaps? Can someone experienced in Django point out the relevant code that is executed between the end of get_queryset() and before the beginning of get()? I can't seem to find any documentation myself.
Thanks!
Related
I am using Django and Graphene to serve a graphql endpoint and I have hit a bit of a problem I can't seem to figure out.
I have following resolver:
class Query(ObjectType):
trainingSession = Field(TrainingSessionType, id=graphene.ID())
trainingSessions = DjangoFilterConnectionField(TrainingSessionType)
#staticmethod
def checked_trainingsession(trainingsession,info):
# returns the trainingsession if a certain logic is fulfilled
# else None
def resolve_trainingSessions(root, info,**kwargs):
ids= kwargs.get('discipline__id')
all = TrainingSession.objects.all()
result = []
for trainingSession in all:
trainingSession = Query.checked_trainingsession(trainingSession,info)
if trainingSession != None:
result.append(trainingSession)
return result
together with the Objects types and Filters:
class TrainingSessionFilter(FilterSet):
discipline__id = GlobalIDMultipleChoiceFilter()
class Meta:
model = TrainingSession
fields = ["discipline__id"]
class TrainingSessionType(DjangoObjectType):
class Meta:
model=TrainingSession
fields="__all__"
filterset_class = TrainingSessionFilter
interfaces = (CustomNode,)
class CustomNode(graphene.Node):
"""
For fetching object id instead of Node id
"""
class Meta:
name = 'Node'
#staticmethod
def to_global_id(type, id):
return id
however when I try to execute a query
query Sessions{
trainingSessions(discipline_Id:[2,3]){
edges{
node{
dateTime,
discipline{
id
}
}
}
}
}
I get the Error:
Traceback (most recent call last):
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\promise\promise.py", line 489, in _resolve_from_executor
executor(resolve, reject)
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\promise\promise.py", line 756, in executor
return resolve(f(*args, **kwargs))
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\graphql\execution\middleware.py", line 75, in make_it_promise
return next(*args, **kwargs)
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\graphene_django\fields.py", line 176, in connection_resolver
iterable = queryset_resolver(connection, iterable, info, args)
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\graphene_django\filter\fields.py", line 62, in resolve_queryset
return filterset_class(data=filter_kwargs, queryset=qs, request=info.context).qs
File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\django_filters\filterset.py", line 193, in __init__
model = queryset.model
graphql.error.located_error.GraphQLLocatedError: 'list' object has no attribute 'model'
I know i should be returning a queryset from resolve_trainingSessions. However, I don't know how to then apply my permission checks on the individual results. The logic is not super complicated, but I can't really wrap it in to a standard Django model filter or Q object.
Thanks for any help or hints.
Ok I managed to solve my issue by following this Idea:
https://docs.graphene-python.org/projects/django/en/latest/authorization/#user-based-queryset-filtering
if user.is_anonymous:
return TrainingSession.objects.none()
if user.is_superuser:
return TrainingSession.objects.filter(filter)
....
Not super elegant but it does its job and it's not too bad.
so as you may have guessed by now, the reason that queries with DjangoFilterConnectionField types has to return a queryset instead of list is so that the pagination works properly, which comes out of the box with it. Unfortunately for users who only care about filtering but not pagination, opting out of returning a queryset is not really possible. So you have three options. (PS I have made some slight other changes into your code snippets, (e.g using #classmethod)
You don't use a DjangoFilterConnectionField
class Query(ObjectType):
trainingSession = Field(TrainingSessionType, id=graphene.ID())
trainingSessions = graphene.List(TrainingSessionType, discipline__id=grapene.List(graphene.ID)
#staticmethod
def checked_trainingsession(trainingsession,info):
# returns the trainingsession if a certain logic is fulfilled
# else None
#classmethod
def resolve_trainingSessions(cls, info, discipline__id):
return [ts for ts in TrainingSession.objects.filter(id__in=discipline_id) if cls.checked_trainingsession(ts, info)]
The advantage of this method is that in the case that your queryset has some items you are allowed to see, then you can still return them, without having to return a queryset object (you can do non-database filtering).
You try really hard to write your checked_trainigsession as a queryset filter - it might seem impossible but after enough sweat you might be able to pull it off.. ive been there.
As you've done here, you sacrifice your ability to return partial data if the user is only allowed to see some items, and just raise an error when it doesn't happen. The way you return queryset.objects.none() is fine, but you can just as easily raise an error (which could be more elegant?)
from graphql_jwt.decorators import superuser_required
class Query(ObjectType):
trainingSession = Field(TrainingSessionType, id=graphene.ID())
trainingSessions = DjangoFilterConnectionField(TrainingSessionType)
#superuser_required
def resolve_trainingSessions(root, info,**filters):
return TrainingSessionFilter(filters).qs
You can also swap out superuser_required for login_required or staff_member_required. Good luck!
Recently I've been trying to do something with this.
Think of the family as a facebook group.
class Family(models.Model):
name = models.CharField(max_length=50)
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='owned_families')
users = models.ManyToManyField(User, related_name='families', blank=True)
let's assume we have this family object called fm, for illustration purpose.
My problem is, The owner is one of the users right? I mean, When someone creates a family, He's now the owner right? he owns it but he's still a user listed in it's users list.
Now, when I create a new family fm , I want to add the fm.owner to fm.users.
Let's talk about what I've tried.
post_save signal doesn't work with m2m. X
m2m_changed happens when the field is changed, not created. X
Overriding save method, lemme illustrate what I tried to acheive. ?
def save(self, *args, **kwargs):
old = self.pk
super(Family, self).save(*args, **kwargs)
if old is None:
print('This actually shows up')
self.users.add(self.owner)
Basically, this saves the pk each time, First time a family is created, Before calling super..... it has no .pk so I'm counting on this to check if it had no pk (On creation).
The problem is self.users.add(self.owner) doesn't work.
I've tried to clone the object as whole and keep track of it like
def save(self, *args, **kwargs):
old = self
super(Family, self).save(*args, **kwargs)
if old is None:
print("This actually doesn't show up")
self.users.add(self.owner)
This actually is terrible, It takes a refernce to self and when calling super...., The selfand it's reference old gets mutated, I just wanted to show this as this question itself might solve someone's problem.
So I solved this by.
import copy
def save(self, *args, **kwargs):
old = copy.deepcopy(self)
super(Family, self).save(*args, **kwargs)
if old is None:
print('This actually shows up')
self.users.add(self.owner)
but self.users.add(self.owner) still doesn't work.
What am I missing?
The problem is probably that in the django admin, the instance is saved first, and only after that the inline formsets and m2m-fields are saved. If the owner is not in there, it will be removed.
You can override some functionality in the admin to remedy this:
class FamilyAdmin(ModelAdmin):
def save_related(self, request, form, formsets, change):
super(FamilyAdmin, self).save_related(request, form, formsets, change)
form.instance.users.add(form.instance.owner)
Furthermore, you can try (note that there are other ways to remove the owner that are not picked up by any signal or other hook) to prevent code from removing the owner:
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
#receiver(m2m_changed, sender=Family.users.through)
def famliy_users_changed(sender, **kwargs):
family = kwargs['instance']
pk_set = kwargs['pk_set']
action = kwargs['action']
if action == "pre_add":
pk_set.add(family.owner_id)
if action == "pre_remove":
pk_set.remove(family.owner_id)
if action == "post_clear":
family.users.add(family.owner)
But generally speaking, you are jumping through those hoops because you are denormalizing your data (putting the owner in users makes that information redundant, forcing you to keep your data correct). Since you always know the owner is one of the users, why not wrap that in a method
class Family(...):
# ...
def members(self):
return User.objects.filter(Q(pk__in=self.users.all()|Q(pk=self.owner_id)))
and access family members through that method?
I have a model named Run with a manager named RunManager and with a custom save() method as follows.
class RunManager(models.Manager):
use_for_related_fields = True
def get_queryset(self):
queryset = super(RunManager, self).get_queryset()
queryset = queryset.filter(archived=False)
return queryset
def unfiltered_runs(self):
queryset = super(RunManager, self).get_queryset()
return queryset
class Run(models.Model):
name = models.CharField(max_length=256)
archived = models.BooleanField(default=False)
objects = RunManager()
def save(self, *args, **kwargs):
# some business logic
super(Run, self).save(*args, **kwargs)
def archive(self):
# Some business logic
self.archived = True
self.save()
def recover_archived(self):
# Some business logic
self.archived = False
self.save()
This was an old code where the run.objects were used at several location, so to hide the archived runs I am using the RunManager.
Everything was working fine, but now we want to unarchive the runs. So I added the unfiltred_runs() method which shows the list of all the runs along with the archived runs. But when I run recove_archived() method i get following error
IntegrityError: UNIQUE constraint failed: run.id
I know the error is because the db is treating it as a new entry with same id.
I know I can completely override the save method but I want to avoid that.
So is there any way to make save method lookup in the unfiltered_runs() queryset instead of regular one.
By following #ivissani's suggestion I modified my recover_archived method as follows. And it is working flawlessly.
def recover_archived(self):
# Some business logic
Run.objects.unfiltered_runs().filter(pk=self.id).update(archived=False)
I'm using Django with Python 3.7 and PyCharm. I'm following this tutorial for learning how to create model and save them into the database -- https://docs.djangoproject.com/en/dev/topics/db/queries/#creating-objects . The tutorial refers to manager for retrieving objects, but not for setting them, so I'm confused as to why I get the below error
Article.objects.create_article(main page, '/path', 'url', 10, 22)
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Manager' object has no attribute 'create_article'
when I'm trying to create and save an object. Below is how I define my object in my models.py file ...
class Article(models.Model):
mainpage = models.ForeignKey(MainPage, on_delete=models.CASCADE,)
path = models.CharField(max_length=100)
url = models.TextField
time = models.DateTimeField(default=datetime.now)
votes = models.IntegerField(default=0)
comments = models.IntegerField(default=0)
def __str__(self):
return self.path
#classmethod
def create(cls, mainpage, path, url, votes, comments):
article = cls(mainpage=mainpage,path=path,url=url,votes=votes,comments=comments)
return article
I'm sure I'm missing something really obvious, but I don't know what it is.
Edit: Many have suggested using the "Article.objects.create" method but below is the output from the console ...
Article.objects.create(mainpage=mainpage, path='/path', url='url', votes=10, comments=22)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/Users/davea/Documents/workspace/mainarticles_project/venv/lib/python3.7/site-packages/django/db/models/manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/Users/davea/Documents/workspace/mainarticles_project/venv/lib/python3.7/site-packages/django/db/models/query.py", line 411, in create
obj = self.model(**kwargs)
File "/Users/davea/Documents/workspace/mainarticles_project/venv/lib/python3.7/site-packages/django/db/models/base.py", line 485, in __init__
raise TypeError("'%s' is an invalid keyword argument for this function" % kwarg)
TypeError: 'url' is an invalid keyword argument for this function
When you define a Model Class, Django jumps into the fray and adds a default Manager to your model under the "objects" property. You can customize the Model and the Manager for different purposes.
Generally methods on your Model should deal with a single instance, or conceptually a row in your database. Model.save(), Model.delete() all act on a single instance or row.
Methods on your manager should usually work with your table as a whole, such as filter(), get(), aggregate(), because these functions perform operations against your table. You also have the create() method on your manager because it adds a row to your table. Further, you can define custom Manager's and assign them to different properties on your model. For example:
class EngineerManager(Manager):
def get_queryset(self):
return super().get_queryset().filter(employee_type="engineer")
class ManagerManager(Manager):
def get_queryset(self):
return super().get_queryset().filter(employee_type="manager")
class Employee(Model):
employee_type = models.CharField()
engineers = EngineerManager()
managers = ManagerManager()
objects = Manager() # This is always added to your models to list all objects
Employee.engineers.filter() # Will only return engineering employees
Employee.objects.filter() # Will return ALL employees
Now to your problem, Article.objects.create_article(...) does not seem to exist, you should probably use Article.objects.create(...) because the default Manager does have a create() method.
Using Article.objects.create(...) will persist a new Article to the database and return it. Otherwise, you could technically use your Article.create(...) method to create an in-memory instance of Article, however it has not been saved to the database so keep in mind you will have to call save() on the instance before it's persisted to your database.
https://docs.djangoproject.com/en/2.1/topics/db/models/
https://docs.djangoproject.com/en/2.1/topics/db/managers/
To create an article in database use manager's create method (params should be named, not positioned, as in your example)
Article.objects.create(mainpage=mainpage, path=path, url=url,
votes=votes, comments=comments)
or you can initialise class instance and then save it. Here params could be positioned or named, no matter.
article = Article(mainpage, path, url, votes, comments)
article.save()
Your method def create(cls, mainpage, path, url, votes, comments): makes no sense, because it duplicates call (__call__ method) of a class, like in my second example. If you want to add some extra logic to object creation, you should define custom Manager class and add method there and then link your model's objects property to you custom manager class like objects = CustomManager()
Here's a Django model class I wrote. This class gets a keyerror when I call get_object_or_404 from Django (I conceive that keyerror is raised due to no kwargs being passed to __init__ by the get function, arguments are all positional). Interestingly, it does not get an error when I call get_object_or_404 from console.
I wonder why, and if the below code is the correct way (ie, using init to populate the link field) to construct this class.
class Link(models.Model)
event_type = models.IntegerField(choices=EVENT_TYPES)
user = models.ForeignKey(User)
created_on = models.DateTimeField(auto_now_add = True)
link = models.CharField(max_length=30)
isActive = models.BooleanField(default=True)
def _generate_link(self):
prelink = str(self.user.id)+str(self.event_type)+str(self.created_on)
m = md5.new()
m.update(prelink)
return m.hexdigest()
def __init__(self, *args, **kwargs):
self.user = kwargs['user'].pop()
self.event_type = kwargs['event_type'].pop()
self.link = self._generate_link()
super(Link,self).__init__(*args,**kwargs)
self.user = kwargs['user'].pop()
self.event_type = kwargs['event_type'].pop()
You're trying to retrieve an entry from the dictionary, and then call its pop method. If you want to remove and return an object from a dictionary, call dict.pop():
self.user = kwargs.pop('user')
Of course, this will fail with a KeyError when "user" is not present in kwargs. You'll want to provide a default value to pop:
self.user = kwargs.pop('user', None)
This means "if "user" is in the dictionary, remove and return it. Otherwise, return None".
Regarding the other two lines:
self.link = self._generate_link()
super(Link,self).__init__(*args,**kwargs)
super().__init__() will set link to something, probably None. I would reverse the lines, to something like this:
super(Link,self).__init__(*args,**kwargs)
self.link = self._generate_link()
You might want to add a test before setting the link, to see if it already exists (if self.link is not None: ...). That way, links you pass into the constructor won't be overwritten.
There's no reason to write your own __init__ for Django model classes. I think you'll be a lot happier without it.
Almost anything you think you want to do in __init__ can be better done in save.
I don't think you need the __init__ here at all.
You are always calculating the value of link when the class is instantiated. This means you ignore whatever is stored in the database. Since this is the case, why bother with a model field at all? You would be better making link a property, with the getter using the code from _generate_link.
#property
def link(self):
....
wonder why, and if the below code is the correct way (ie, using __init__ to populate the link field) to construct this class.
I once got some problems when I tried to overload __init__
In the maillist i got this answer
It's best not to overload it with your own
__init__. A better option is to hook into the post_init signal with a
custom method and in that method do your process() and
make_thumbnail() calls.
In your case the post_init-signal should do the trick and implementing __init__ shouldn't be necessary at all.
You could write something like this:
class Link(models.Model)
event_type = models.IntegerField(choices=EVENT_TYPES)
user = models.ForeignKey(User)
created_on = models.DateTimeField(auto_now_add = True)
link = models.CharField(max_length=30)
isActive = models.BooleanField(default=True)
def create_link(self):
prelink = str(self.user.id)+str(self.event_type)+str(self.created_on)
m = md5.new()
m.update(prelink)
return m.hexdigest()
def post_link_init(sender, **kwargs):
kwargs['instance'].create_link()
post_init.connect(post_link_init, sender=Link)
>>> link = Link(event_type=1, user=aUser, created_on=datetime.now(), link='foo', isActive=True)
providing keyword unique for link = models.CharField(max_length=30, unique=True) could be helpful, too. If it is not provided, get_object_or_404 may won't work in case the same value in the link-field exists several times.
signals and unique in the django-docs