I am learning DRF and I've now run into a problem that has stalled me for days. The app is a zumba class application and I'm using DRF to create an API on top of it. The part I am trying to build right now is the part where a user can add himself to a zumba class(so, he has to be able to update a manytomany field) What I'd like the API to do when the user register himself to the class(PUT, or PATCH), is to take his username, that we get from the authentication, and add him to "myusers" field. But since the PUT is empty, the API keeps complaining that "myusers" is required.
Is there a way to tell the API that "myusers" is not required in the PUT request since it is extracted from the authentication token? (If I manually create a PUT request with "myusers": [{"id": 9}] in the body, it works but I'd like to avoid adding that client side since the data passed is not even used.)
The serializer(all the reado-only fields are to make sure the user cannot update them):
class UserActivityClassesSerializer(serializers.ModelSerializer):
activitytypename = serializers.SlugRelatedField(source='activitytype', slug_field='name', read_only=True)
activitytype = ActivityTypeSerializer(read_only=True)
myusers = MyUsersSerializer(many=True) # serializers.HyperlinkedRelatedField(many=True, view_name='myusers-detail', read_only=True)
uri = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Activity
fields = [
'uri',
'id',
'name',
'date',
'activitytype',
'activitytypename',
'status',
'myusers',
]
read_only_fields = [
'uri',
'id',
'name',
'date',
'activitytype',
'activitytypename',
'status',
]
def get_uri(self, obj):
request = self.context.get('request')
return api_reverse("api-activities:classes-detail", kwargs={"id": obj.id}, request=request)
The view
class ActivityViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated]
queryset = Activity.objects.all()
lookup_field = 'id'
def get_serializer_class(self):
if self.request.user.is_staff or self.action == 'create':
return AdminActivityClassesSerializer
else:
return UserActivityClassesSerializer
def perform_update(self, serializer):
instance = self.get_object()
request = serializer.context['request']
auth_user = request.user
qs = instance.myusers.filter(id=auth_user.id)#we verify is the user is already registered, and if yes, we remove him
if qs:
print('delete')
instance.myusers.remove(auth_user)
return instance
else:
result = verify_valid_season_pass(auth_user, instance)
if result == 1:
print('add')
instance.myusers.add(auth_user.id)
else:
raise serializers.ValidationError("No valid seasonpass found.")
The model that gets updated:
class Activity(models.Model):
CLOSE = 0
OPEN = 1
ACTIVITY_STATUS_CHOICES = [
(CLOSE, 'Closed'),
(OPEN, 'Open'),
]
name = models.CharField('Activity Name', max_length=64, blank=False, null=False)
date = models.DateTimeField('Start Date & Time', blank=False, null=False)
activitytype = models.ForeignKey(ActivityType, blank=False, null=False, default=1, on_delete=models.DO_NOTHING)
status = models.IntegerField('Status', choices=ACTIVITY_STATUS_CHOICES, default=OPEN,) # 1 = open, 0 = closed
myusers = models.ManyToManyField("users.User")
def __str__(self):
return self.name
Any clues?
Have you tried
myusers = MyUsersSerializer(many=True, required=False)
Related
I have a Django REST project. I have a models User, Store and Warehouse.
And I have a module with marketplace parser, that gets data from marketplace API. In this module there is a class Market and a method "get_warehouses_list". This method returns a JSON with a STORE's warehouse list.
Examle answer:
{
"result": [
{
"warehouse_id": 1,
"name": "name1",
"is_rfbs": false
},
{
"warehouse_id": 2,
"name": "name2",
"is_rfbs": true
}
]
}
What I have to do is to make creating and updating methods to set and update this warehouse list into my MySQL DB (with creating an endpoint for setting and updating this data).
I don't know what is incorrect in my code, but when I send POST request to my endpoint in urls.py
router.register("", WarehouseApi, basename="warehouse")
I get 400 error instead of setting warehouse list into my DB.
My code:
user/models.py
class User(AbstractUser):
username = models.CharField(
max_length=150,
unique=True,
null=True)
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
unique=True,
editable=False)
store/models.py
`
class Store(models.Model):
user = models.ForeignKey(
User,
on_delete=models.PROTECT)
name = models.CharField(max_length=128,
blank=True)
type = models.PositiveSmallIntegerField(
choices=MARKET,
default=1,
verbose_name="Type API")
api_key = models.CharField(max_length=128)
client_id = models.CharField(max_length=128)
warehouses/models.py
class Warehouse(models.Model):
store = models.ForeignKey(
Store,
on_delete=models.СASCAD, null=True)
warehouse_id = models.BigIntegerField(
unique = True,
null = True)
name = models.CharField(
max_length=150)
is_rfbs = models.BooleanField(default=False)
`
serializers.py
`
class WarehouseSerializer(serializers.ModelSerializer):
class Meta:
model = Warehouse
fields = '__all__'
store = serializers.CharField(max_length=50)
warehouse_id = serializers.IntegerField()
name = serializers.CharField(max_length=100)
is_rfbs = serializers.BooleanField()
is_default_warehouse = serializers.BooleanField()
class WarehouseUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Warehouse
fields = ('name', 'is_rfbs',)
def save(self, **kwargs):
self.instance.name = self.validated_data["name"]
self.instance.is_rfbs = self.validated_data["is_rfbs"]
self.instance.save
returself.instance
views.py
class WarehouseApi(ModelViewSet):
def get_queryset(self):
return Warehouse.objects.filter(
store__user_id=self.request.user.pk)\
.order_by('-warehouse_id')
def get_serializer_class(self):
if self.request.method in ("PUT", "PATCH"):
return WarehouseUpdateSerializer
return WarehouseSerializer
def create(self, request, *args, **kwargs):
st = Store.objects.filter(user=self.request.user, # getting all stores with marketplace type = 1
type=1)
for e in st:
api = Market(api_key=e.api_key, # call parser class Market
client_id=e.client_id)
data = api.get_warehouses_list() # call Market's method 'get_warehouses_list'
if len(data['result']) > 0:
for wh in data['result']:
alreadyExists = Warehouse.objects.filter( # check if warehouses with such ID already exists
warehouse_id=wh.get(
'warehouse_id')).exists()
if alreadyExists:
return Response({'message':'Warehouse ID already exists'})
else:
wh_data = {
'warehouse_id': wh.get('warehouse_id'),
'name': wh.get('name'),
'is_rfbs': wh.get('is_rfbs')
}
Warehouse.objects.create(**wh_data)
warehouses = Warehouse.objects.filter(
marketplace=1, store__in=st).order_by(
'-warehouse_id')
s = WarehouseSerializer(warehouses, many=True)
return Response(status=200, data=s.data)
else:
return Response(status=400,
data={"Error": "Store has no warehouses"})
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
api = Market(api_key=instance.store.api_key,
client_id=instance.store.client_id)
warehouses = Warehouse.objects.filter(store__user_id=self.request.user,
store__type=1) # get all store's warehouses
if warehouses:
for warehouse in warehouses:
r = api.get_warehouses_list() # call Market's method 'get_warehouses_list'
if len(r['result']) > 0:
for wh in r['result']:
if serializer.validated_data["name"] != wh['name']:
instance.name = wh['name']
instance.save(['name'])
if serializer.validated_data["is_rfbs"] != wh['is_rfbs']:
instance.name = wh['is_rfbs']
instance.save(['is_rfbs'])
serializer = WarehouseUpdateSerializer(instance)
return Response(serializer.data, {
'status': status.HTTP_200_OK,
'message': 'Warehouse updated successfully'
})
else:
return Response({
'message': 'Store has no warehouses'
})
else:
return Response({
'message': 'There are no saved warehouses in DB'
})
`
The 400 Error might be caused by a number of reasons. Do you have any logging information you can provide? Either from the Python logs or using the browser developer tools?
In your own code you are returning a 400 code if the variable data is empty. Are you sure you are not hitting this validation?
If nothing turns up, I advise you to add some exception dealing and logging capabilities to your own code as it seems to be very unprotected and post that information here if needed.
i am trying to create a notifications system but am facing issues doing so.
here is my code:
I created a Notifications model where each entry is a notification. Every time a new entry is made into my SalesTask model , it invokes my signal handler which will create a new entry into my Notifications Model. I will then pass this model into my context and render it in my HTML.
class Notifications(models.Model):
notifications_id = models.AutoField(primary_key=True)
user = models.ForeignKey(User)
time = models.DateTimeField(auto_now=True)
message = models.TextField(max_length=100 ,default ='test')
object_url = models.CharField(max_length=500, default ='test')
is_read = models.BooleanField(default=False)
def CreateTaskNotification(sender,**kwargs):
if kwargs['created']:
notification = Notifications.objects.create(user = kwargs['instance'].salesExtra.username,
message = 'You have been assigned a new task',
object_url = kwargs['instance'].get_absolute_url(self)
)
post_save.connect(CreateTaskNotification,sender=SalesTask)
The issue with this is that my SalesTask Models :
class SalesTask(models.Model):
sales_status= (
('p1','Phase 1'),
('p2','Phase 2'),
('p3','Phase 3'),
('p4','Phase 4'),
)
sales_priority= (
('Urgent','Urgent'),
('Medium','Medium'),
('Low','Low'),
)
task_id = models.AutoField(primary_key=True)
salesExtra= models.ManyToManyField('SalesExtra')
sales_project= models.ForeignKey('SalesProject',on_delete=models.CASCADE)
title = models.TextField(max_length=50 , default='Your Title' )
description = models.TextField(max_length=200 , default='Your Description' )
priority = models.TextField(max_length=10 , choices= sales_priority ,default='Low' )
date_time = models.DateTimeField(auto_now=True)
status = models.TextField(max_length=10, choices= sales_status ,default='p1')
due_date = models.DateTimeField(default=timezone.now)
def __str__(self):
return str(self.task_id)
def get_absolute_url(self):
return reverse('sales-task')
has a many to many field with an extended model of User model (SalesExtra) . This is most probably why i am getting the error message :
'ManyRelatedManager' object has no attribute 'user'
This is my views for creating a new instance of a task:
class SalesTaskDetailView(CreateView):
model = SalesTaskingComments
template_name = 'rnd/task_details.html'
fields = ['comment']
exclude = ['comment_id']
def get_context_data(self, **kwargs):
pk = self.kwargs['pk']
context = super(SalesTaskDetailView, self).get_context_data(**kwargs)
context['sales_task'] = SalesTask.objects.filter(task_id = pk)
context['comments'] = SalesTaskingComments.objects.filter(salesTask__task_id__contains= pk)
return context
def form_valid(self, form):
task = get_object_or_404(SalesTask, task_id=self.kwargs['pk'])
user = get_object_or_404(SalesExtra, user=self.request.user)
form.instance.salesTask = task
form.instance.salesExtras = user
return super(SalesTaskDetailView, self).form_valid(form)
My question therefore is :
1. Is there a way to create a new entry for every SalesExtra object ?
2. Is this the best method for creating a Notifications system? (i am using django 3.0 therefore many applications like django-notifications or django-activities-stream is not compatible.)
If you create a through table for the many to many relationship between SalesTask and SalesExtra, you can add the signal handler to this through table so that whenever a relationship is made between the 2 (a user has presumably been added) a notification will be made. If you save a SalesTask model form that has a multi select for the extras this will be triggered for each selection
class SalesTask(models.Model):
salesExtra = models.ManyToManyField('SalesExtra', through='SalesTaskExtra')
class SalesExtra(models.Model):
pass
class SalesTaskExtra(models.Model):
task = models.ForeignKey(SalesTask, on_delete=models.CASCADE)
extra = models.ForeignKey(SalesExtra, on_delete=models.CASCADE)
def CreateTaskNotification(sender, **kwargs):
if kwargs['created']:
Notifications.objects.create(
user=kwargs['instance'].extra.username,
message='You have been assigned a new task',
object_url=kwargs['instance'].task.get_absolute_url()
)
post_save.connect(CreateTaskNotification, sender=SalesTaskExtra)
I have am implementing a follower and followers system in my drf api.
My relationship model and serializer:
models.py
class Relationship(models.Model):
user = models.ForeignKey(User, related_name="primary_user", null=True, blank=True)
related_user = models.ForeignKey(User, related_name="related_user", null=True, blank=True)
incoming_status = models.CharField(max_length=40, choices=RELATIONSHIP_CHOICE, default=RELATIONSHIP_CHOICE[0])
def __str__(self):
return self.user.username + " " + self.incoming_status + " " + self.related_user.username
serializers.py
class RelationshipSerializer(serializers.ModelSerializer):
outgoing_status = serializers.SerializerMethodField()
class Meta:
model = models.Relationship
fields = (
'user',
'related_user',
'incoming_status',
'outgoing_status',
)
def get_outgoing_status(self, obj):
related_user = obj.related_user
user = obj.user
try:
query = models.Relationship.objects.get(user=related_user, related_user=user)
user_id = query.incoming_status
except models.Relationship.DoesNotExist:
user_id = "none"
return user_id
views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = models.User.objects.all()
serializer_class = serializers.UserSerializer
#detail_route(methods=['get'], url_path='relationship/(?P<related_pk>\d+)')
def relationship(self, request, pk, related_pk=None):
user = self.get_object()
query = models.Relationship.objects.filter(user=user)&\
models.Relationship.objects.filter(related_user__pk=related_pk)
serializer = serializers.RelationshipSerializer(query, many=True)
return Response(serializer.data)
Incoming status is your relationship to the user and outgoing status is the users relationship to you to so for example this can return
[
{
"user": 2,
"related_user": 1,
"incoming_status": "follows",
"outgoing_status": "none"
}
]
This means you follow the user and the user doesn't follow you back
When you follow a user my code works fine because it returns "incoming_status": "follows" and it then checks for the outgoing status in the serializers. However when the:
query = models.Relationship.objects.filter(user=user) &
models.Relationship.objects.filter(related_user__pk=related_pk)
query in views.py returns null the meaning that the incoming status is none the serializer outputs [] because the query is empty. How do I make incoming_status = "none" if the query is empty?
Thanks in advance
Error message
Cannot assign "<SimpleLazyObject: <User: dip7777>>": "ImageTarget.uploaderclient" must be a "UploaderClient" instance.
I'm working on Python 3.4.3 and Django 1.8 and using Django Rest Framework 3.
I'm using the DRF browsable API for testing.
What I'm trying to do is to attach the currently logged in user to the file that he uploads.
Whenever I try to do a POST and upload a file with a logged in user, it throws up the above error.
I have a couple of UploaderClient created and the user dip7777 is one such user tied to a UploaderClient. I have logged in using that user.
What I am able to accomplish with the current code is edit the currently uploaded file ie. upload a new file in place of the current file using the ImageTargetDetail view.
(I had uploaded a couple of files and tied them to UploaderClients using the admin interface)
What I want to do is upload a new file using the ImageTargetList view and not replace a preexisting one.
But the error shows up and I do not understand how to assign the current(logged in) uploaderclient instance to the ImageTarget's uploaderclient
I have two models 1)UploaderClient
class UploaderClient(models.Model):
user = models.OneToOneField(User)
company_name = models.CharField(_('company name'), max_length=100)
class Meta:
verbose_name = _('uploaderclient')
verbose_name_plural = _('uploaderclients')
def __str__(self):
return 'UploaderClient: {}'.format(self.user.username)
and 2) ImageTarget
class ImageTarget(models.Model):
uploaderclient = models.ForeignKey('authenticateclients.UploaderClient', related_name='imagetargets')
targetName = models.CharField(max_length=50, unique=True)
imageWidth = models.IntegerField()
targetFile = models.FileField(upload_to=get_upload_imagetargetfile_name)
in two different apps authenticateclients and clientupload
My serializers are:
class UploaderClientSerializer(serializers.HyperlinkedModelSerializer):
username = serializers.CharField(source='user.username')
email = serializers.CharField(source='user.email', required=False, allow_null=True)
password = serializers.CharField(source='user.password', write_only=True, required=False)
date_joined = serializers.DateTimeField(source='user.date_joined', read_only=True)
last_login = serializers.DateTimeField(source='user.last_login', read_only=True)
company_name = serializers.CharField()
imagetargets = serializers.HyperlinkedRelatedField(many=True, view_name='imagetargetsdetail', read_only=True)
url = serializers.HyperlinkedIdentityField(view_name='uploaderclientsdetail')
class Meta:
model = UploaderClient
fields = ('url', 'email', 'username', 'company_name', 'date_joined', 'last_login', 'password','imagetargets',)
read_only_fields = ('user.date_joined', 'user.last_login',)
and
class ImageTargetSerializer(serializers.HyperlinkedModelSerializer):
uploaderclient = UploaderClientSerializer(read_only=True,required=False)
targetFile = serializers.FileField(label='TargetFile')
url = serializers.HyperlinkedIdentityField(view_name='imagetargetsdetail')
class Meta:
model = ImageTarget
fields = ('url','uploaderclient','targetName','imageWidth','targetFile',)
def get_validation_exclusions(self):
exclusions = super(ImageTargetSerializer, self).get_validation_exclusions()
return exclusions + ['uploaderclient']
My views are
class UploaderClientList(generics.ListAPIView):
queryset = UploaderClient.objects.all()
serializer_class = UploaderClientSerializer
class UploaderClientDetail(generics.RetrieveAPIView):
queryset = UploaderClient.objects.all()
serializer_class = UploaderClientSerializer
and
class ImageTargetList(generics.ListCreateAPIView):
queryset = ImageTarget.objects.all()
serializer_class = ImageTargetSerializer
def perform_create(self, serializer):
instance = serializer.save(uploaderclient=self.request.user)
return super(ImageTargetList, self).perform_create(serializer)
permission_classes = (permissions.IsAuthenticated,IsOwnerOrNothing,)
class ImageTargetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = ImageTarget.objects.all()
serializer_class = ImageTargetSerializer
permission_classes = (permissions.IsAuthenticated,IsOwnerOrNothing,)
#api_view(('GET',))
def api_root(request, format=None):
return Response({
'uploaderclients': reverse('uploaderclients', request=request, format=format),
'imagetargets': reverse('imagetargets', request=request, format=format),
})
In ImageTargetList.perform_create:
instance = serializer.save(uploaderclient=self.request.user)
However uploaderclient should not be a user.
In my code CreateNoticeForm is working fine and all data gets saved perfectly except
many to many field which is tags in my notice model.Although it is working on admin site and gets saved.
here is my code
models.py
from django.db import models
from django.contrib.auth.models import User
from wysihtml5.fields import Wysihtml5TextField
# Create your models here.
class Tag(models.Model):
# For notice tags
name = models.CharField(max_length=70, unique=True)
def __str__(self):
return self.name
class Notice(models.Model):
# Notice Store
headline = models.CharField(max_length=140)
description = Wysihtml5TextField()
file_name = models.FileField(upload_to='static/noticefiles/', blank=True)
created_by = models.ForeignKey(User)
fors = models.CharField(max_length=1, choices=(('F','Faculty'),('S','Student'),) )
last_date = models.DateField()
tags = models.ManyToManyField(Tag, blank=True)
post_time = models.DateTimeField(auto_now_add=True)
update_time = models.DateTimeField(auto_now=True)
def __str__(self):
return self.headline
forms,py
FORS_CHOICES = (('F','Faculty'),('S','Student'))
class CreateNoticeForm(ModelForm):
fors = forms.ChoiceField(label="Related To",
choices=FORS_CHOICES,
)
class Meta:
model = Notice
fields = ('headline', 'description',
'fors', 'last_date', 'tags','file_name')
widgets = {
'description': Wysihtml5BootstrapWidget(),
'last_date': SelectDateWidget()
}
def __init__(self, *args, **kwargs):
super(CreateNoticeForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'create_notice_form_id'
self.helper.form_class = 'form-horizontal'
self.helper.form_method = 'post'
self.helper.label_class = 'col-lg-2'
self.helper.field_class = 'col-lg-8'
self.helper.layout = Layout(
Fieldset('Create Notice',
'headline',
'description',
Field('fors', label='Audience'),
MultiWidgetField('last_date',
attrs=(
{'style': 'width: 33.33%; display: inline-block;'}
)
),
'tags',
'file_name',
FormActions(
Submit('save', 'Create Notice',
css_class='btn-warning col-lg-offset-2'),
),
),
views.py
def create_notice(request):
context = RequestContext(request)
posted = False
if request.method=='POST':
create_notice_form = CreateNoticeForm(data=request.POST, files=request.FILES)
if create_notice_form.is_valid():
cnf = create_notice_form.save(commit=False)
cnf.created_by = request.user
cnf.save()
posted = True
else:
print(create_notice_form.errors)
else:
create_notice_form = CreateNoticeForm()
return render_to_response('notices/createnotice1.html',
{'create_notice_form': create_notice_form,
'posted': posted,},
context)
You have to call save_m2m():
cnf = create_notice_form.save(commit=False)
cnf.created_by = request.user
cnf.save()
create_notice_form.save_m2m()
Excerpt from the documentation:
If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save many-to-many data for an instance until the instance exists in the database.
To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you’ve manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data.
You have to Call the following after validating the form:-
if create_notice_form.is_valid():
parent = create_notice_form.save(commit=False)
parent.save()
create_notice_form.save_m2m()
I hope this will help you