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()
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!
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!
I'm going to convert all my APIs into gRPC calls. At the moment I was able to transfer all the ViewSet into gRPC(sample code added end of this question). But ModelViewSet, it get an error like this.
Traceback (most recent call last):
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/venv/lib/python3.6/site-packages/grpc/_server.py", line 435, in _call_behavior
response_or_iterator = behavior(argument, context)
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/servicers/tenant/main.py", line 15, in get_tenant
data = ClientViewSet().list(request=original_request, )
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/common/lib/decorators.py", line 128, in wrapper_format_response
final_data = call_func(func, self, request, transaction, exception, *args, **kwargs)
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/common/lib/decorators.py", line 99, in call_func
return func(self, request, *args, **kwargs)
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/api_v1/viewsets.py", line 471, in list
data = super().list(request, *args, **kwargs).data
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/venv/lib/python3.6/site-packages/rest_framework/mixins.py", line 38, in list
queryset = self.filter_queryset(self.get_queryset())
File "/home/wasdkiller/PycharmProjects/ocsa/dataengine-service/venv/lib/python3.6/site-packages/rest_framework/generics.py", line 158, in filter_queryset
queryset = backend().filter_queryset(self.request, queryset, self)
AttributeError: 'ClientViewSet' object has no attribute 'request'
So my viewsets.py look like this (format_response decorator convert it to Response object)
class ClientViewSet(viewsets.ModelViewSet):
queryset = Client.objects.all()
serializer_class = ser.ClientSerializer
#format_response(exception=False)
def list(self, request, *args, **kwargs):
data = super().list(request, *args, **kwargs).data
# data = {}
return data, True, HTTPStatus.OK, 'data retrieve successfully'
When I call this as an API, it works perfectly. But I want to do the same thing without calling an API. Here how I was solving it,
from django.http import HttpRequest
from rest_framework.request import Request
# creating request object
django_request = HttpRequest()
django_request.method = 'GET'
drf_request = Request(django_request)
data = ClientViewSet().list(request=drf_request)
print(f'data: {data.data}')
The problem with the super() function in the ClientViewSet, but if I uncomment data = {} and comment out the calling super() function, it works both API and the above method. I go through inside the DRF code base where error occurred, when calling the API the self object has the request object and above method it doesn't exist.
In short, you need to call as_view() on the viewset and map the http verbs, and then call that function with a proper HttpRequest.
All views in django end up being functions, DRF is sugar on top of that. A "ViewSet" doesn't exist in the normal way, as a standalone class, after routing is complete.
django_request = HttpRequest()
django_request.method = 'GET'
my_view = ClientViewSet.as_view({'get': 'list', 'post':'create'})
data = my_view(request=django_request)
print(f'data: {data.data}')
If you want the detail routes (users/37, ...), then you need to create a new view function mapping to those viewset functions. This can't be the same view function because http get now needs to point to a different function on the viewset than in the list case. See routers.py source for whats going on and what is mapped where.
# map http 'get' to the 'retrive' function on the viewset
my_view = ClientViewSet.as_view({'get': 'retrieve', ...})
# pass in the kwarg the URL routing would normally extract
client_pk = 9329032
data = my_view(request=django_request, pk=client_pk)
If you want to see what all the mappings for your viewset are, then you an print them out using this snippet:
router = SimpleRouter()
router.register("client", ClientViewSet, basename="client")
for url in router.urls: # type: URLPattern
print(f"{url.pattern} ==> {url.callback}")
for verb, action in url.callback.actions.items():
print(f" {verb} -> {action}")
# output will be something like this
^client/$ ==> <function ClientViewSet at 0x11b91c280>
get -> list
^client/(?P<pk>[^/.]+)/$ ==> <function ClientViewSet at 0x11b91c3a0>
get -> retrieve
put -> update
patch -> partial_update
delete -> destroy
The kwargs you pass to this view will depend on the settings in your ViewSet for things like lookup_url_kwarg, but in most cases they will be simple.
I want to separate some logic from model and group it in a property, just like Django does with model managers (object property). I fact, something like ForeignKey but without database representation.
I have something like this:
class Remote(object):
def __init(self, *args, **kwargs)
self.post = ... # how to get to post instance?
def synchronize(self):
# this function requires Post object access
print(self.post.name)
class Post(models.Model):
name = models.CharField(max_length=100)
remote = Remote()
...
for post in Post.objects.all():
post.remote.synchronize()
Question
How to modify above code to get access to Post object in Remote object?
Additional question
Is it possible to determine if Remote object has been called from Post instance (post.remote... – like above) or Post class (Post.remote...)?
What you want here can be achieved with descriptors.
In order for it to work, you need to define a __get__ method in your class that you want to be accessible as an attribute of another class.
A simple example for your case will look like this:
class Remote:
def __init__(self, post)
self.post = post
def synchronize(self):
print(self.post.name)
class RemoteDescriptor:
def __get__(self, obj):
if not obj:
return self
remote = getattr(obj, '_remote', None)
if not remote:
remote = Remote(obj)
obj._remote = remote
return remote
class Post(models.Model):
name = models.CharField(max_length=100)
remote = RemoteDescriptor()
Explanation:
In the above code, every time you call an attribute remote of your Post model, __get__ method of the RemoteDescriptor will be invoked. First check for obj is to make sure that descriptor is called from other object, not directly. Two classes Remote and RemoteDescriptor are needed here in order for you to be able to add custom methods inside your descriptor accessible using dot (e.g. post.remote.calculate())
Note also that I am placing the instance of Remote to the dict of Post on first invokation and on all subsequent calls, object will be returned from there.
You should also check a great article on descriptors on RealPython.
I am using Django 2.1.1.
I have a model Analysis that, among other fields, contains a ForeignKey to a MyFile model (a model I wrote to handle files):
from polymorphic.models import PolymorphicModel
from django.db.models import Model, DateTimeField, FileField, SET_NULL
from django.db.models.signals import pre_delete
class MyFile(Model):
file = FileField(upload_to='./', null=False, blank=False)
description = CharField(max_length=255, null=True, blank=True)
date_added = DateTimeField(auto_now_add=True)
#receiver(pre_delete, sender=MyFile)
def mymodel_delete(sender, instance, **kwargs):
"""
To delete the file connected to the `sender` class: receive the pre_delete signal
and delete the file associated with the model instance.
"""
instance.file.delete(False)
class Analysis(PolymorphicModel):
# ... other fields ...
file_results = ForeignKey(MyFile, on_delete=SET_NULL,
related_name='file_results',
null=True, blank=True)
Analysis is a PolymorphicModel for reasons related to the bigger project.
In Analysis.file_results I set on_delete=SET_NULL because I want to allow an Analysis instance to exist even without a file_result, which can be populated later.
Let's suppose I have added a few files (the MyFile table has a few rows) and a few Analysis instances. Now, if I want to delete the file related to one of the instances of Analysis I do:
a = Analysis.objects.get(pk=0)
a.file_results.delete()
a.save()
but I get the following error:
File "/Users/mtazzari/djangos/views.py" in update_job_refs
377. a.save()
File "/Users/mtazzari/anaconda/envs/djangos/lib/python3.6/site-packages/polymorphic/models.py" in save
83. return super(PolymorphicModel, self).save(*args, **kwargs)
File "/Users/mtazzari/anaconda/envs/djangos/lib/python3.6/site-packages/django/db/models/base.py" in save
670. "unsaved related object '%s'." % field.name
ValueError: save() prohibited to prevent data loss due to unsaved
related object 'file_results'.
The mymodel_delete function that is called on pre_delete signal works correctly as the file gets actually deleted from the file system.
However, I really don't understand how to solve the ValueError.
Interestingly, I notice that the following lines work fine, i.e. do not raise any ValueError, get the file deleted from the file system, and the FK in a.file_results set to Null:
a = Analysis.objects.get(pk=0)
tmp = a.file_results
a.file_results = None
tmp.file_results.delete()
a.save()
But, is this a proper way of doing this? What is the best practice for deleting a related object?
Thanks!
First, note that you don't need to save() just because of the delete(). The delete() will update the database as required.
That said, it's reasonable to want to continue using the instance to do other operations, leading to a save(). The reason you're getting the error is that the a.file_results Python object still exists, and references a database row that is now missing. The documentation for delete() mentions this:
This only deletes the object in the database; the Python instance will still exist and will still have data in its fields.
So if you want to continue to work with the instance object, just set the attribute to None yourself. Similar to your code above, except you don't need the temp object.
a = Analysis.objects.get(pk=0)
a.file_results.delete()
a.file_results = None
# ... more operations on a
a.save() # no error