Using Django's GenericForeignKey with Async Graphene Subscription - django

I've implemented graphql_ws to subscribe to updates from a Notification model that uses multiple GenericForeignKeys.
My setup works well, except when I try to query information from those foreign objects. I then get the following error:
graphql.error.located_error.GraphQLLocatedError: You cannot call this from an async context - use a thread or sync_to_async.
From what I seem to understand, that's because some database query operations are being done outside the async context of get_notifications (that is, in the resolve_actor function). But I'm really not clear on how I can pull the operations of resolve_actor
into the async context.
I've unsuccessfully tried using prefetch_related (plus I'm not sure it'll work with multiple content types per Django's docs).
Here's the code
models.py
class Notification(TimeStampedModel):
# ...
actor_content_type = models.ForeignKey(ContentType, related_name='notify_actor', on_delete=models.CASCADE)
actor_object_id = models.CharField(max_length=255)
actor = GenericForeignKey('actor_content_type', 'actor_object_id')
# ...
schema.py
class ActorTypeUnion(graphene.Union):
"""
All possible types for Actors
(The object that performed the activity.)
"""
class Meta:
types = (UserType,) # here's there's only one type, but other fields have multiple
class NotificationType(DjangoObjectType):
actor = graphene.Field(ActorTypeUnion)
def resolve_actor(self, args):
if self.actor is not None:
model_name = self.actor._meta.model_name
app_label = self.actor._meta.app_label
model = ContentType.objects.get(app_label=app_label, model=model_name)
return model.get_object_for_this_type(pk=self.actor_object_id)
return None
# ...
class Meta:
model = Notification
class Subscription(graphene.ObjectType):
unread_notifications = graphene.List(NotificationType)
async def resolve_unread_notifications(self, info, **kwargs):
user = info.context['user']
if user.is_anonymous:
raise Exception('Not logged in!')
#database_sync_to_async
def get_notifications(user):
notifications = Notification.objects.filter(
recipient=user,
read=False,
organization=user.active_organization,
)
return [notifications]
while True:
await asyncio.sleep(1)
yield await get_notifications(user)
The query (things works well except when I query fields on actor)
subscription {
unreadNotifications {
id,
read,
actor {
... on UserType {
__typename,
id
}
}
}
}
Full traceback
Traceback (most recent call last):
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/graphql/execution/executor.py", line 452, in resolve_or_error
return executor.execute(resolve_fn, source, info, **args)
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/graphql/execution/executors/asyncio.py", line 74, in execute
result = fn(*args, **kwargs)
File "/Users/benjaminsoukiassian/Projects/logbook-back/logbook/notifications/schema.py", line 52, in resolve_actor
model = ContentType.objects.get(app_label=app_label, model=model_name)
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 431, in get
num = len(clone)
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 262, in __len__
self._fetch_all()
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 1324, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 51, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1173, in execute_sql
cursor = self.connection.cursor()
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/utils/asyncio.py", line 24, in inner
raise SynchronousOnlyOperation(message)
graphql.error.located_error.GraphQLLocatedError: You cannot call this from an async context - use a thread or sync_to_async.

Related

Role choice field data ins't saved for UserRegsitrationForm django

The following error message appears after I submit the SignUpform:
'NoneType' object has no attribute '_inc_path'
This issue is related to the role choice field of my CustomUser model. The models function perfectly without the role field and all forms are displayed and saved correctly.
I suspect my choice field form does not pass/save correctly the input values to the CustomUser model.
Any input would be highly appreciated.
Models.py:
class CustomUser(AbstractUser):
display_name = models.CharField(verbose_name=("Display name"), max_length=30, help_text=("Will be shown e.g. when commenting"))
...
country = CountryField(blank=True, null=True)
...
role = models.CharField(choices = ROLES, max_length = 50, default = "regular_user",)
...
class Meta:
ordering = ['last_name']
def get_absolute_url(self):
return reverse('account_profile')
def __str__(self):
return f"{self.username}: {self.first_name} {self.last_name}" ```
forms.py:
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30, label=_("First name"))
last_name = forms.CharField(max_length=30, label=_("Last name"))
display_name = forms.CharField(max_length=30, label=_("Display name"), help_text=_("Will be shown e.g. when commenting."))
role = forms.ChoiceField(choices = ROLES, label="Role", initial='Regular_user', widget=forms.Select(), required=True)
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.display_name = self.cleaned_data['display_name']
user.role = self.cleaned_data['role']
user.save()
users/create.html:
{% extends "wagtailusers/users/create.html" %}
{% block extra_fields %}
...
{% include "wagtailadmin/shared/field_as_li.html" with field=form.role %}
...
{% endblock extra_fields %}
settings.py:
AUTH_USER_MODEL = 'userauth.CustomUser'
WAGTAIL_USER_CREATION_FORM ='userauth.forms.WagtailUserCreationForm'
WAGTAIL_USER_EDIT_FORM = 'userauth.forms.WagtailUserEditForm'
WAGTAIL_USER_CUSTOM_FIELDS = ['display_name',... 'role', ...]
ACCOUNT_SIGNUP_FORM_CLASS = 'userauth.forms.SignupForm'
Error log:
2022-10-17 10:16:35,894: Internal Server Error: /accounts/signup/
Traceback (most recent call last):
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/django/views/generic/base.py", line 84, in view
return self.dispatch(request, *args, **kwargs)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/django/utils/decorators.py", line 46, in _wrapper
return bound_method(*args, **kwargs)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/allauth/decorators.py", line 20, in wrap
resp = function(request, *args, **kwargs)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/django/utils/decorators.py", line 46, in _wrapper
return bound_method(*args, **kwargs)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/django/views/decorators/debug.py", line 92, in sensitive_post_parameters_wrapper
return view(request, *args, **kwargs)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/allauth/account/views.py", line 234, in dispatch
return super(SignupView, self).dispatch(request, *args, **kwargs)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/allauth/account/views.py", line 77, in dispatch
response = super(RedirectAuthenticatedUserMixin, self).dispatch(
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/allauth/account/views.py", line 207, in dispatch
return super(CloseableSignupMixin, self).dispatch(request, *args, **kwargs)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/django/views/generic/base.py", line 119, in dispatch
return handler(request, *args, **kwargs)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/allauth/account/views.py", line 105, in post
response = self.form_valid(form)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/allauth/account/views.py", line 252, in form_valid
return complete_signup(
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/allauth/account/utils.py", line 183, in complete_signup
signals.user_signed_up.send(
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/django/dispatch/dispatcher.py", line 176, in send
return [
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/django/dispatch/dispatcher.py", line 177, in <listcomp>
(receiver, receiver(signal=self, sender=sender, **named))
File "/home/teki/fommestyuta/users/models.py", line 38, in create_user_group_and_pages
home.add_child(instance=article_index_page)
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/treebeard/mp_tree.py", line 1083, in add_child
return MP_AddChildHandler(self, **kwargs).process()
File "/home/teki/.virtualenvs/bup/lib/python3.9/site-packages/treebeard/mp_tree.py", line 377, in process
newobj.path = self.node.get_last_child()._inc_path()
AttributeError: 'NoneType' object has no attribute '_inc_path'
create_user_group_and_pages:
from django.db import models
from django.contrib.auth.models import Group, Permission
from django.dispatch import receiver
from allauth.account.signals import user_signed_up
from wagtail.core.models import Page, GroupPagePermission, GroupCollectionPermission, Collection
from cms.models import ArticleIndexPage
#receiver(user_signed_up)
def create_user_group_and_pages(sender, **kwargs):
"""
When a new user signs up create a unique group and page for them.
Assign it the appropriate permission for admin, page and collection access.
"""
# Grab the new user
user = kwargs['user']
# Create a group object that matches their username
new_group, created = Group.objects.get_or_create(name=user.username)
# Add the new group to the database
user.groups.add(new_group)
# Create new permission to access the wagtail admin
access_admin = Permission.objects.get(codename='access_admin')
# Add the permission to the group
new_group.permissions.add(access_admin)
# Now start creating page access
# First find the homepage
home = Page.objects.get(slug='home').specific
# Create unique PersonIndexPage for the user
article_index_page = ArticleIndexPage(title=user.username)
# Add PersonIndexPage to homepage as a child
home.add_child(instance=article_index_page)
# Save new page as first revision
article_index_page.save_revision()
# Create new add GroupPagePermission
GroupPagePermission.objects.create(
group=new_group,
page=article_index_page,
permission_type='add'
)
# Create new GroupCollectionPermission for Profile Images collection
GroupCollectionPermission.objects.create(
group=new_group,
collection=Collection.objects.get(name='Images'),
permission=Permission.objects.get(codename='add_image')
)
It looks like the page tree data in your database has become inconsistent, causing it to fail when creating the new ArticleIndexPage. (Specifically, the record for the 'home' page shows that it has existing child pages, meaning that it has to look at those child page records to find the correct position for the new page - but when it tries retrieving those child pages, none are being returned.) This may have happened due to a previous error occurring part-way through creating or deleting a page.
To fix this, run: ./manage.py fixtree

How to use ListSerializer with a ModelSerializer?

I am attempting to create a POST endpoint using DRF ListSerializer to create a list of LogLevel objects.
I have tried to serialize the foreign key using PrimaryKeyRelatedField without success.
models.py
relevant fields for LogLevel model. note foreign key to node model
#associated node
node = models.ForeignKey(Node, on_delete=models.DO_NOTHING,
related_name="log_levels")
#logger name
name = models.CharField(max_length=32, choices=LOGGERS)
# Current log level
level = models.IntegerField(default=INFO,
choices=LOG_LEVELS)
# Timestamps
created_datetime = models.DateTimeField(auto_now_add=True)
updated_datetime = models.DateTimeField(auto_now=True,
blank=True, null=True)
serializers.py
class LogLevelListSerializer(serializers.ListSerializer):
def create(self, validated_data):
log_levels = [LogLevel(**item) for item in validated_data]
levels = LogLevel.objects.bulk_create(log_levels)
return levels
class LogLevelCreateUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = LogLevel
fields = "__all__"
list_serializer_class = LogLevelListSerializer
LogLevel view
class LogLevelList(MethodSerializerMixin,
generics.ListCreateAPIView):
"""
Log Level list API Endpoint.
"""
method_serializer_classes = {
("POST",): LogLevelCreateUpdateSerializer
}
def get_queryset(self):
"""
Queryset to use for endpoint.
"""
return LogLevel.objects.all()
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
# check if many is required
if "data" in kwargs:
data = kwargs["data"]
# check if many is required
if isinstance(data, list):
kwargs["many"] = True
return serializer_class(*args, **kwargs)
MethodSerializerMixin
from rest_framework import exceptions
class MethodSerializerMixin(object):
"""
Utility class to apply a different serializer class depending
on the request method.
For example:
method_serializer_classes = {
("GET", ): MyModelListViewSerializer,
("PUT", "PATCH"): MyModelCreateUpdateSerializer
}
"""
method_serializer_classes = None
def get_serializer_class(self):
assert self.method_serializer_classes is not None, (
f"Expected view {self.__class__.__name__} should contain "
f"method_serializer_classes to get right serializer class."
)
for methods, serializer_cls in self.method_serializer_classes.items():
if self.request.method in methods:
return serializer_cls
raise exceptions.MethodNotAllowed(self.request.method)
Im passing in a json list of simple objects in the request. node is the foreign key id:
[{
"name": "logger1",
"level": 2,
"node": 1
},
{
"name": "logger2",
"level": 3,
"node": 1
}]
I expect the objects to be created and displayed to the client with success status. Currently, the objects are created in the db successfully but a 500: Server Error is returned and this is the stacktrace I see on Django server:
Internal Server Error: /api/clustering/loglevel/set/
Traceback (most recent call last):
File "/opt/cisco/env/iris/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/django/core/handlers/base.py", line 145, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/django/core/handlers/base.py", line 143, in _get_response
response = response.render()
File "/opt/cisco/env/iris/lib/python3.6/site-packages/django/template/response.py", line 106, in render
self.content = self.rendered_content
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/response.py", line 72, in rendered_content
ret = renderer.render(self.data, accepted_media_type, context)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/renderers.py", line 724, in render
context = self.get_context(data, accepted_media_type, renderer_context)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/renderers.py", line 697, in get_context
'post_form': self.get_rendered_html_form(data, view, 'POST', request),
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/renderers.py", line 520, in get_rendered_html_form
return self.render_form_for_serializer(serializer)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/renderers.py", line 528, in render_form_for_serializer
serializer.data,
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 765, in data
ret = super(ListSerializer, self).data
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 266, in data
self._data = self.get_initial()
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 600, in get_initial
return self.to_representation(self.initial_data)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 683, in to_representation
self.child.to_representation(item) for item in iterable
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 683, in <listcomp>
self.child.to_representation(item) for item in iterable
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 527, in to_representation
ret[field.field_name] = field.to_representation(attribute)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/relations.py", line 257, in to_representation
return value.pk
AttributeError: 'int' object has no attribute 'pk'
python==3.6
django==2.2.2
drf==3.8.2
The serializer.data property is only valid if you have a saved an instance to the serializer.
Either call serializer.save() or use serializer.validated_data to access data prior to saving.
Checkout this link for further information.
Had to handle this error by updating to_representation method on the PrimaryKeyRelatedField class
class NodePrimaryKeyField(serializers.PrimaryKeyRelatedField):
"""
Custom DRF serializer field for proper handling of
Node Foreign Key by ListSerializer on validation error
"""
def to_representation(self, value):
"""
Return pk value of serialized Node object
if available else return given ID value
"""
if self.pk_field is not None:
return self.pk_field.to_representation(value.pk)
return getattr(value, 'pk', value)

duplicate key value violates unique constraint itemstaticlabel_item_id_f9405967_uniq

I know, this problem happens because the code is trying to add an entry to the database with a combination of unique keys that already exist. But my question is: why and how can I prevent this? It doesn't make sense to me that this is happening, given my logic.
I have this piece of code that basically stores some static label for an invoice. The idea is to create the label on the fly if it doesn't exist, instead of manually inserting it in a lot of places in the code.
Whenever an Item is generated, an ItemInv is also created, and by saving the latter, I check if a static label exists. If it doesn't, a new one is created. The code to get (or create) the static label is also called when displaying an Invoice / Draft.
The error seems to happen right there. The label doesn't exist, a new one is created, but somehow this happens twice? I mean, I really don't get why that code would try to add the same item two times. Could it be a thread issue?
I am not using get_or_create because some attributes on ItemStaticInv (static_account_label, static_agreement_label...) can be updated and not unique. Before creating the invoice, a draft has the same structure and the data can be changed, including these labels.
class ItemInv(managers.Model):
"""
Relation between Item and Invoice
"""
item = models.ForeignKey('Item')
invoice = models.ForeignKey('Invoice')
[...]
class Meta:
unique_together = ('item', 'invoice')
def save(self, *args, **kwargs):
iteminv = super(ItemInv, self).save(*args, **kwargs)
# If the item does not have the static information, one should be created
self.item.get_invoice_static(self.invoice)
return iteminv
class Item(managers.Model):
"""
Item to be put on an invoice
"""
serial = models.AutoField(primary_key=True) # Software license
product = models.ForeignKey('Product', verbose_name='Product')
account = models.ForeignKey('Account', verbose_name='Account', related_name='items')
[...]
def get_invoice_static(self, document):
try:
return self.document_labels.get(invoice=document)
except ObjectDoesNotExist:
return ItemStaticLabel.objects.create(
item=self,
invoice=document,
static_account_label=str(self.account),
static_customer_label=str(self.account.customer),
static_agreement_label=str(self.account.agreement)
)
class ItemStaticLabel(managers.Model):
"""
We must store static information about the Item on an invoice.
"""
item = models.ForeignKey('Item', related_name='document_labels')
invoice = models.ForeignKey('Invoice', related_name='item_labels')
static_account_label = models.CharField(max_length=256, null=True, blank=True)
static_customer_label = models.CharField(max_length=256, null=True, blank=True)
static_agreement_label = models.CharField(max_length=256, null=True, blank=True)
static_expiration_label = models.DateField(max_length=256, null=True, blank=True)
class Meta:
unique_together = ('item', 'invoice')
This has several flaws, but this system was developed by someone else and is on production. The amount of structural changes I can do are pretty much limited. Therefore I gotta try to understand what is causing this issue.
Traceback:
File "/home/apps/sites/invoice/source/v3/models.py" in get_invoice_static
1105. return self.document_labels.get(invoice=document)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/manager.py" in manager_method
85. return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/query.py" in get
380. self.model._meta.object_name
During handling of the above exception (ItemStaticLabel matching query does not exist.), another exception occurred:
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
65. return self.cursor.execute(sql, params)
The above exception (duplicate key value violates unique constraint "v3_itemstaticlabel_item_id_f9405967_uniq"
DETAIL: Key (item_id, invoice_id)=(1840, 1578) already exists.
) was the direct cause of the following exception:
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/core/handlers/exception.py" in inner
41. response = get_response(request)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/core/handlers/base.py" in _legacy_get_response
249. response = self._get_response(request)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/core/handlers/base.py" in _get_response
187. response = self.process_exception_by_middleware(e, request)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/core/handlers/base.py" in _get_response
185. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python3.4/contextlib.py" in inner
30. return func(*args, **kwds)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/views/generic/base.py" in view
68. return self.dispatch(request, *args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/views/generic/base.py" in dispatch
88. return handler(request, *args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/views/generic/detail.py" in get
116. context = self.get_context_data(object=self.object)
File "/home/apps/sites/invoice/source/web/views/invoice.py" in get_context_data
280. context['document_html'] = services.document_to_html(obj)
File "/home/apps/sites/invoice/source/v3/services.py" in document_to_html
1550. 'account': set_[0].get_invoice_static(document).static_account_label,
File "/home/apps/sites/invoice/source/v3/models.py" in get_invoice_static
1112. static_agreement_label=str(self.account.agreement)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/manager.py" in manager_method
85. return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/query.py" in create
394. obj.save(force_insert=True, using=self.db)
File "/home/apps/sites/invoice/source/v3/managers.py" in save
724. super().save(*args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/base.py" in save
808. force_update=force_update, update_fields=update_fields)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/base.py" in save_base
838. updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/base.py" in _save_table
924. result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/base.py" in _do_insert
963. using=using, raw=raw)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/manager.py" in manager_method
85. return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/query.py" in _insert
1076. return query.get_compiler(using=using).execute_sql(return_id)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/sql/compiler.py" in execute_sql
1107. cursor.execute(sql, params)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
65. return self.cursor.execute(sql, params)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/utils.py" in __exit__
94. six.reraise(dj_exc_type, dj_exc_value, traceback)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/utils/six.py" in reraise
685. raise value.with_traceback(tb)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
65. return self.cursor.execute(sql, params)
Exception Type: IntegrityError at /invoice-menu/customer/251/invoices/1578/
Exception Value: duplicate key value violates unique constraint "v3_itemstaticlabel_item_id_f9405967_uniq"
DETAIL: Key (item_id, invoice_id)=(1840, 1578) already exists.
View
class InvoiceDetailView(DetailView):
model = Invoice
template_name = 'invoice_detail.html'
pk_url_kwarg = 'invoice_key'
context_object_name = 'invoice'
def get_context_data(self, **kwargs):
context = super(InvoiceDetailView, self).get_context_data(**kwargs)
obj = context[self.context_object_name]
obj = models.Invoice.objects.filter(pk=obj.pk).select_related(
'customer',
'customer__original',
'currency',
).prefetch_related(
'citems',
'citems__assocs',
'citems__assocs__product',
'iteminv_set',
'iteminv_set__item',
).first()
context['document_html'] = services.document_to_html(obj)
return context
Services
def document_to_html(document):
"""
Renders the invoice to html.
"""
from v3.models import Item, Maintenance, Invoice, CustomInvoiceLine
from web.collection_utils import collapse_list
data = {}
items = Item.objects.on_invoice(document)
mains = Maintenance.objects.on_invoice(document)
rows = get_invoice_product_rows(document)
# merge the lists
rows['itemrows'].extend(rows['mainrows'])
include_summary = len(rows['itemrows']) > 0 and not Invoice.objects.filter(creditted_by=document)
# calc VAT lines
vats = {}
for ir in rows['itemrows']:
row = ir['line']
vat = ir['objs'][0].vat
rate = ir['objs'][0].vat_rate
total = (Decimal(rate / 100) * ir['rowtotal']).quantize(Decimal('.01'), rounding=ROUND_HALF_UP)
try:
vats[vat]['rownums'].append(row)
vats[vat]['total'] += total
except:
vats[vat] = dict(rownums=[row, ], message=ir['objs'][0].vat_message, total=total, rate=rate)
for mr in rows['mainrows']:
row = mr['line']
vat = mr['objs'][0].vat
rate = mr['objs'][0].vat_rate
total = (Decimal(rate / 100) * mr['rowtotal']).quantize(Decimal('.01'), rounding=ROUND_HALF_UP)
try:
vats[vat]['rownums'].append(row)
# Changed it to not add any value because that will produce wrong vat output.
vats[vat]['total'] += 0
except:
vats[vat] = dict(rownums=[row, ], message=mr['objs'][0].vat_message, total=total, rate=rate)
for cr in rows['citemrows']:
row = cr['line']
vat = cr['obj'].vat
rate = cr['obj'].vat_rate
total = (Decimal(rate / 100) * cr['rowtotal']).quantize(Decimal('.01'), rounding=ROUND_HALF_UP)
try:
vats[vat]['rownums'].append(row)
vats[vat]['total'] += total
except:
vats[vat] = [row, ]
vats[vat] = dict(rownums=[row, ], message=cr['obj'].vat_message, total=total, rate=rate)
vats = [(k, collapse_list(v['rownums']), v['message'], v['total'], v['rate']) for (k, v) in vats.items()]
vats = sorted(vats, key=lambda x: x[1])
# Gather data for the license agreement overview
# A list of lists, where the inner lists contain items that match on product and account
matched_item_sets = []
items = list(items) + list([m.serial for m in mains])
for item in items:
# Find a set to add this item to
for set_ in matched_item_sets:
if item.product == set_[0].product and item.account == set_[0].account:
if item not in set_:
set_.append(item)
break
else:
# No matching set found. Create a new one
matched_item_sets.append([item])
# Package the item sets nicely with meta information
summary = []
for set_ in matched_item_sets:
summary.append({
'account': set_[0].get_invoice_static(document).static_account_label,
'owner': set_[0].get_invoice_static(document).static_customer_label,
'agreement': set_[0].get_invoice_static(document).static_agreement_label,
'product': set_[0].product,
'licenses': collapse_list(set_),
})
data['inv'] = document
data['lines'] = rows['itemrows']
data['citems'] = rows['citemrows']
data['vats'] = vats
data['lagree'] = summary
data['includeSummary'] = include_summary
data['cilines'] = CustomInvoiceLine.objects.filter(invoice=document, on_invoice=True)
data['base_url'] = settings.SITE_BASE_URL
tpl = document.template
tpl = DjangoTemplate(tpl.content)
return tpl.render(Context(data))

ValueError: "<Notification: Notification object>" needs to have a value for field "notification" before this many-to-many relationship can be used

I have this model:
class Notification(BaseTimestampableModel):
# TYPES CONSTANTS HERE
# TYPE_CHOICES DICT HERE
sender = models.ForeignKey(User, related_name='sender_notifications')
receivers = models.ManyToManyField(User, related_name='receiver_notifications')
type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES)
data = models.TextField()
sent = models.BooleanField(default=False)
class Meta:
verbose_name = _('Notification')
verbose_name_plural = _('Notifications')
def send(self):
# Logic for sending notification here
self.sent = True
self.save()
For other hand, I've this "static" class:
class ChatNotifications:
#staticmethod
def message_created(message, chat):
"""
Send a notification when a chat message is created
to all users in chat except to the message's sender.
"""
sender = message.user
data = {
'text': message.text,
'phone': str(sender.phone_prefix) + str(sender.phone),
'chatid': chat.uuid.hex,
'time': timezone.now().timestamp(),
'type': 'text',
'msgid': message.uuid.hex
}
notification = Notification(
sender=sender,
receivers=chat.get_other_users(sender),
type=Notification.TYPE_CHAT_MESSAGE,
data=json.dumps(data)
)
notification.send()
But when I call ChatNotifications.message_created(msg, chat) (message and chat are previusly saved), I get this error:
ValueError: "<Notification: Notification object>" needs to have a value for field "notification" before this many-to-many relationship can be used.
Researching on Google, I try do this, but this don't solved my problem.
With debug, I checked the error is throwing when Model constructor is called.
This is the trace:
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/home/vagrant/petycash/apps/chats/notifications.py", line 45, in message_created
data=json.dumps(data)
File "/usr/local/lib/python3.5/dist-packages/django/db/models/base.py", line 550, in __init__
setattr(self, prop, kwargs[prop])
File "/usr/local/lib/python3.5/dist-packages/django/db/models/fields/related_descriptors.py", line 499, in __set__
manager = self.__get__(instance)
File "/usr/local/lib/python3.5/dist-packages/django/db/models/fields/related_descriptors.py", line 476, in __get__
return self.related_manager_cls(instance)
File "/usr/local/lib/python3.5/dist-packages/django/db/models/fields/related_descriptors.py", line 783, in __init__
(instance, self.source_field_name))
ValueError: "<Notification: Notification object>" needs to have a value for field "notification" before this many-to-many relationship can be used.
You can’t associate Notification with a User until it’s been saved.
So you have to save Notification first then you can add receivers
notification = Notification(
sender=sender,
type=Notification.TYPE_CHAT_MESSAGE,
data=json.dumps(data)
).save()
# If chat.get_other_users(sender) return a queryset
receivers = chat.get_other_users(sender)
for receiver in receivers:
notification.receivers.add(receiver)
# or you can also simply assign the whole list as it's already empty after new create
# >>> notification.receivers = recievers
notification.send()

Saving M2M field with Tastypie

I'm building an API with Tastypie, and I've run into an issue when saving a many-to-many field.
I have a model call Pest and another called Call, and Call has a field called pests representing the pests that can be applied to a call. These already exist and the user can choose one or more to apply to that call - there is no intention to create them at the same time as the Call object.
By default, I get the following error when I try to create a new Call via POST:
{"error_message": "Cannot resolve keyword 'url' into field. Choices are: baitpoint, call, description, id, name, operator", "traceback": "Traceback (most recent call last):\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 217, in wrapper\n response = callback(request, *args, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 459, in dispatch_list\n return self.dispatch('list', request, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 491, in dispatch\n response = method(request, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 1357, in post_list\n updated_bundle = self.obj_create(bundle, **self.remove_api_resource_names(kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2150, in obj_create\n return self.save(bundle)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2300, in save\n m2m_bundle = self.hydrate_m2m(bundle)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 964, in hydrate_m2m\n bundle.data[field_name] = field_object.hydrate_m2m(bundle)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/fields.py\", line 853, in hydrate_m2m\n m2m_hydrated.append(self.build_related_resource(value, **kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/fields.py\", line 653, in build_related_resource\n return self.resource_from_uri(self.fk_resource, value, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/fields.py\", line 573, in resource_from_uri\n obj = fk_resource.get_via_uri(uri, request=request)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 838, in get_via_uri\n return self.obj_get(bundle=bundle, **self.remove_api_resource_names(kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2125, in obj_get\n object_list = self.get_object_list(bundle.request).filter(**kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/query.py\", line 655, in filter\n return self._filter_or_exclude(False, *args, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/query.py\", line 673, in _filter_or_exclude\n clone.query.add_q(Q(*args, **kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py\", line 1266, in add_q\n can_reuse=used_aliases, force_having=force_having)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py\", line 1134, in add_filter\n process_extras=process_extras)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py\", line 1332, in setup_joins\n \"Choices are: %s\" % (name, \", \".join(names)))\n\nFieldError: Cannot resolve keyword 'url' into field. Choices are: baitpoint, call, description, id, name, operator\n"}
So I had a look and found this answer, which seemed to cover a similar situation. I added the hydrate_pests method to the CallResource class as follows:
class AbstractModelResource(ModelResource):
class Meta:
authorization = DjangoAuthorization()
authentication = ApiKeyAuthentication()
cache = SimpleCache(timeout=10)
always_return_data = True
class FilteredByOperatorAbstractModelResource(AbstractModelResource):
def authorized_read_list(self, object_list, bundle):
user = bundle.request.user
site_user = SiteUser.objects.get(user=user)
return object_list.filter(operator=site_user.operator)
class PestResource(FilteredByOperatorAbstractModelResource):
class Meta(AbstractModelResource.Meta):
queryset = Pest.objects.all()
resource_name = 'pest'
allowed_methods = ['get']
class CallResource(AbstractModelResource):
client = fields.ForeignKey(ClientResource, 'client')
operator = fields.ForeignKey(OperatorResource, 'operator')
pests = fields.ManyToManyField(PestResource, 'pests', null=True)
class Meta(AbstractModelResource.Meta):
queryset = Call.objects.all()
resource_name = 'call'
def hydrate_pests(self, bundle):
pests = bundle.data.get('pests', [])
pest_ids = []
for pest in pests:
m = re.search('\/api\/v1\/pests\/(\d+)\/', str(pest))
try:
id = m.group(1)
pest_ids.append(id)
except AttributeError:
pass
bundle.data['pests'] = Pest.objects.filter(id__in=pest_ids)
return bundle
The pests field is getting passed through as follows:
0: "/api/v1/pests/6/"
1: "/api/v1/pests/7/"
And the pest URL's are showing up correctly when I run bundle.data.get('pests', []) - if I use PDB to set a trace, I can verify that the URLs are being passed through, and Pest.objects.filter(id__in=pest_ids) is returning the correct items. However, although the HTTP POST request is successful, the pests field is not being updated to reflect the new data.
Can anyone see where I've gone wrong? Am I correct in passing through a list of the Pest objects to bundle.data['pests'], or is this not how I should be passing this data through to that field?
What actually gets passed through to bundle.data is as follows:
{'pests': [<Pest: Rats>, <Pest: Mice>], 'notes': u'Blah', 'first_choice_visit_time': u'2013-07-18T02:02', 'client': u'/api/v1/client/28/', 'date': u'2013-07-18', 'second_choice_visit_time': u'2014-03-03T03:02'}
The bundle data holds dictionaries. You're passing it a list of QuerySet objects. Try appending .values() to your queryset.