DRF - 'str' object has no attribute 'pk' - python

I've got a front-end that sends JSON to the back-end to switch the input of a digital audio stream, with an optional time component to schedule the switch for the future. Here are the components of making this work:
from views.py:
class SwitchStreamView(views.APIView):
"""
A custom endpoint for switching inputs of radio streams
"""
queryset = RadioStream.objects.all()
def post(self, request, format=None):
serializer = serializers.RadioSwitchSerializer(data=request.data, many=True)
serializer.is_valid()
for stream in serializer.data:
if stream.schedule_time is None:
tasks.switch_stream(stream.mnemonic, stream.current_input)
else:
tasks.schedule_switch(stream.mnemonic, stream.current_input, stream.schedule_time)
return HttpResponse('')
from serializers.py:
class RadioSwitchSerializer(serializers.ModelSerializer):
schedule_time = serializers.SerializerMethodField()
def get_schedule_time(self, obj):
return obj.get('schedule_time', None)
class Meta:
model = RadioStream
fields = ('mnemonic', 'current_input', 'schedule_time')
The issue I'm having is that however I try and send a test JSON snippet, I'm getting errors. With this setup, sending
[
{
"mnemonic": "TEST",
"current_input": "TEST"
}
]
results in the error 'str' object has no attribute 'pk', but if I change RadioSwitchSerializer(data=request.data, many=True) to many=False, and send
{
"mnemonic": "TEST",
"current_input": "TEST"
}
I get the response 'str' object has no attribute 'schedule_time' instead.
My plan was to use mnemonic to identify the stream, and current_input to identify which input to switch it to. My questions are; Why is this not working, and should I be using a non-Model serializer for this custom action instead of trying to fit the action into the existing fields of the model?
Edit: Here is the traceback
Internal Server Error: /api/switch/
Traceback (most recent call last):
File "...\lib\site-packages\django\core\handlers\exception.py", line 35, in inner
response = get_response(request)
File "...\lib\site-packages\django\core\handlers\base.py", line 128, in _get_response
response = self.process_exception_by_middleware(e, request)
File "...\lib\site-packages\django\core\handlers\base.py", line 126, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "...\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "...\lib\site-packages\django\views\generic\base.py", line 69, in view
return self.dispatch(request, *args, **kwargs)
File "...\lib\site-packages\rest_framework\views.py", line 489, in dispatch
response = self.handle_exception(exc)
File "...\lib\site-packages\rest_framework\views.py", line 449, in handle_exception
self.raise_uncaught_exception(exc)
File "...\lib\site-packages\rest_framework\views.py", line 486, in dispatch
response = handler(request, *args, **kwargs)
File "...\radio_switching\views.py", line 45, in post
for stream in serializer.data:
File "...\lib\site-packages\rest_framework\serializers.py", line 738, in data
ret = super(ListSerializer, self).data
File "...\lib\site-packages\rest_framework\serializers.py", line 266, in data
self._data = self.get_initial()
File "...\lib\site-packages\rest_framework\serializers.py", line 573, in get_initial
return self.to_representation(self.initial_data)
File "...\lib\site-packages\rest_framework\serializers.py", line 656, in to_representation
self.child.to_representation(item) for item in iterable
File "...\lib\site-packages\rest_framework\serializers.py", line 656, in <listcomp>
self.child.to_representation(item) for item in iterable
File "...\lib\site-packages\rest_framework\serializers.py", line 500, in to_representation
ret[field.field_name] = field.to_representation(attribute)
File "...\lib\site-packages\rest_framework\relations.py", line 259, in to_representation
return value.pk
AttributeError: 'str' object has no attribute 'pk'

replace your views.py by this snippet,
class SwitchStreamView(views.APIView):
"""
A custom endpoint for switching inputs of radio streams
"""
queryset = RadioStream.objects.all()
def post(self, request, format=None):
serializer = serializers.RadioSwitchSerializer(data=request.data, many=True)
serializer.is_valid()
for stream in serializer.data:
if 'schedule_time' not in stream:
tasks.switch_stream(stream['mnemonic'], stream['current_input'])
else:
tasks.schedule_switch(stream['mnemonic'], stream['current_input'], stream['schedule_time'])
return HttpResponse('')
The reason for the error is, you are trying to access a python dict using dot operator. To access dictionary elements, you can use the familiar square brackets along with the key to obtaining it's value. Here is the official doc
EDIT
AttributeError: 'str' object has no attribute 'pk' this error because, somewhere you trying to access .pk from a str object
Reproducing the error
In [7]: my_str = 'this is just a string'
In [8]: my_str.pk
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-8-5bc39c990550> in <module>()
----> 1 my_str.pk
AttributeError: 'str' object has no attribute 'pk'

There were a few issues, but it turns out the root issue was that serlializer.data was for whatever reason returning a string and not an array of objects. I ended up replacing the ModelSerializer with a regular Serializer here:
class RadioSwitchSerializer(serializers.Serializer):
mnemonic = serializers.CharField(max_length=20)
new_input = serializers.CharField(max_length=20)
schedule_time = serializers.DateTimeField(allow_null=True)
As per Jerin's answer, I also fixed up the lines accessing the stream dictionary to use square brackets rather than the dot operator. These two things have fixed the issue.

Related

Access cleaned data from django admin for pdf processing

As a newbie, I am currently trying to add a print button to every existing django admin view of a project.
The goal is to make it not depend on a single model I could resolve manually for printing but every existing model which contains foreign keys to other models an so on.
For simplicity I thought it would be the best to just get the cleaned_data of the form and process it for the pdf.
I alread added the print button, the path url and so on and it will create a pdf file for me.
What I am not able to do is to access the forms cleaned_data from my BaseAdmins (extends the ModelAdmin) class like this:
form = BaseForm(request.POST)
if form.is_valid():
data = form.cleaned_data
It will just give me random kinds of errors, like object has no attribute 'is_bound'
So I think that I am generally wrong with the context where I am trying to get the cleaned_data from the form. Every tutorial I found is just showing how to get the data but not fully resolved if it contains foreign keys and not in which context.
Could you please clear up for me where it would make sense to pass any kind of form data maybe as session data or post body to a print view where I can process it.
Thank you very much for reading, hope I was able to describe my problem, feel free to ask.
Edit
This is the BaseForm I changed variable names for internal reasons:
class BaseForm(ModelForm):
def clean_custom(self):
another_model = self.cleaned_data.get('AnotherModel')
custom_models = self.cleaned_data.get('CustomModel')
custom_models_allowed = CustomModel.objects.filter(AnotherModel=another_model)
custom_models_was_list = True
if not custom_models:
return
if not isinstance(custom_models, Iterable):
custom_models_was_list = False
custom_models = [custom_models]
for custom_model in custom_models:
if another_model and custom_model not in custom_models_allowed:
custom_models_allowed = [custom_model.titel for custom_model in custom_models_allowed]
custom_models_allowed = ', '.join(custom_models_allowed)
raise ValidationError(
f'{custom_model} is not part of {another_model}. For selection: {custom_models_allowed}'
)
if custom_models_was_list:
return custom_models
else:
return custom_models[0]
I'm trying to add cleaned_data in my BaseAdmin class where I have access to request and get the following trace:
AttributeError
AttributeError: type object 'CustomForm' has no attribute 'is_bound'
Traceback (most recent call last)
File ".../.env/lib/python3.9/site-packages/django/contrib/staticfiles/handlers.py", line 65, in __call__
return self.application(environ, start_response)
File ".../.env/lib/python3.9/site-packages/django/core/handlers/wsgi.py", line 141, in __call__
response = self.get_response(request)
File ".../.env/lib/python3.9/site-packages/django/core/handlers/base.py", line 75, in get_response
response = self._middleware_chain(request)
File ".../.env/lib/python3.9/site-packages/django/core/handlers/exception.py", line 36, in inner
response = response_for_exception(request, exc)
File ".../.env/lib/python3.9/site-packages/django/core/handlers/exception.py", line 90, in response_for_exception
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
File ".../.env/lib/python3.9/site-packages/django/core/handlers/exception.py", line 125, in handle_uncaught_exception
return debug.technical_500_response(request, *exc_info)
File ".../.env/lib/python3.9/site-packages/django_extensions/management/technical_response.py", line 40, in null_technical_500_response
raise exc_value.with_traceback(tb)
File ".../.env/lib/python3.9/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File ".../.env/lib/python3.9/site-packages/django/core/handlers/base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File ".../.env/lib/python3.9/site-packages/django/core/handlers/base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File ".../.env/lib/python3.9/site-packages/django/contrib/admin/options.py", line 606, in wrapper
return self.admin_site.admin_view(view)(*args, **kwargs)
File ".../.env/lib/python3.9/site-packages/django/utils/decorators.py", line 142, in _wrapped_view
response = view_func(request, *args, **kwargs)
File ".../.env/lib/python3.9/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
response = view_func(request, *args, **kwargs)
File ".../.env/lib/python3.9/site-packages/django/contrib/admin/sites.py", line 223, in inner
return view(request, *args, **kwargs)
File ".../admin/base_admin.py", line 203, in change_view
if self.form.is_valid(self.form):
File ".../.env/lib/python3.9/site-packages/django/forms/forms.py", line 185, in is_valid
return self.is_bound and not self.errors
AttributeError: type object 'CustomForm' has no attribute 'is_bound'
This is not a standard question format, you are supposed to give us the code for the piece of code where the error occurs, (i.e. the base_admin.py file) but what you have provided is just 3 lines of it.
But so far I can see you are not checking whether the request is of the type of post or not. Pay attention to the if condition at the beginning of the method
def edit(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = PurposeForm(request.POST)
# check whether it's valid:
if form.is_valid():
for key, value in form.cleaned_data.items():
# etc

user_id = getattr(user, api_settings.USER_ID_FIELD) AttributeError: 'str' object has no attribute 'id

I am trying to authenticate a user through their email using DRF but so far it's only errors i've been getting.
This is the class that handles the email verification
class VerifyEmail(GenericAPIView):
def get(self, request):
token = request.GET.get('token')
try:
payload = jwt.decode(token, settings.SECRET_KEY) # Decodes the user token and the secret key to get the user ID
print(payload)
user = User.objects.get(id=payload['user_id']) # Gotten the user ID
if not user.is_verified: # Runs an if statement to see if the user has been verified already
user.is_verified = True
user.save()
data = {"confirmation_message": "Your account has been verified"}
return Response(data, status=status.HTTP_200_OK)
except jwt.ExpiredSignatureError as identifier:
error = {"expired_activation_link": "The activation link has expired"}
return Response(error, status=status.HTTP_400_BAD_REQUEST)
except jwt.DecodeError as identifier:
error = {"invalid_token": "The token is invalid request a new one"}
return Response(error, status=status.HTTP_400_BAD_REQUEST)
and this is the error i keep getting
Internal Server Error: /auth/register
Traceback (most recent call last):
File "/mnt/c/Users/Somtochukwu/Desktop/cultural-exchange/proj/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/mnt/c/Users/Somtochukwu/Desktop/cultural-exchange/proj/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/mnt/c/Users/Somtochukwu/Desktop/cultural-exchange/proj/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "/mnt/c/Users/Somtochukwu/Desktop/cultural-exchange/proj/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
return self.dispatch(request, *args, **kwargs)
File "/mnt/c/Users/Somtochukwu/Desktop/cultural-exchange/proj/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "/mnt/c/Users/Somtochukwu/Desktop/cultural-exchange/proj/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "/mnt/c/Users/Somtochukwu/Desktop/cultural-exchange/proj/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
raise exc
File "/mnt/c/Users/Somtochukwu/Desktop/cultural-exchange/proj/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
response = handler(request, *args, **kwargs)
File "/mnt/c/Users/Somtochukwu/Desktop/cultural-exchange/SRC/users/views.py", l
ine 43, in post
token = RefreshToken.for_user(user_email).access_token
File "/mnt/c/Users/Somtochukwu/Desktop/cultural-exchange/proj/lib/python3.8/sit
e-packages/rest_framework_simplejwt/tokens.py", line 161, in for_user
user_id = getattr(user, api_settings.USER_ID_FIELD)
AttributeError: 'str' object has no attribute 'id'
please how can i fix this?
In your views.py Line 43,
token = RefreshToken.for_user(user_email).access_token
This is wrong because the RefreshToken.for_user() method accepts a User object as argument and not a string and that's why you are getting that error.
Reference to for_user docs..
You can also see the relevant RefreshToken.for_user method's code on their github here.

Models and ModelForms: needs to have a value for field "id" before this many-to-many relationship can be used

I'm using a Django Model with some many-to-many fields. I'm also using a ModelForm to generate the associated form. It is my understanding that, provided nothing else is overridden, Django should be able to handle many-to-many fields being saved in the ModelForm?
For me, attempting to do this is causing this error:
Internal Server Error: /cameramodel/create/
Traceback (most recent call last):
File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python3.8/site-packages/django/views/generic/base.py", line 71, in view
return self.dispatch(request, *args, **kwargs)
File "/usr/local/lib/python3.8/site-packages/django/contrib/auth/mixins.py", line 52, in dispatch
return super().dispatch(request, *args, **kwargs)
File "/usr/local/lib/python3.8/site-packages/django/views/generic/base.py", line 97, in dispatch
return handler(request, *args, **kwargs)
File "/usr/local/lib/python3.8/site-packages/django/views/generic/edit.py", line 172, in post
return super().post(request, *args, **kwargs)
File "/usr/local/lib/python3.8/site-packages/django/views/generic/edit.py", line 141, in post
if form.is_valid():
File "/usr/local/lib/python3.8/site-packages/django/forms/forms.py", line 185, in is_valid
return self.is_bound and not self.errors
File "/usr/local/lib/python3.8/site-packages/django/forms/forms.py", line 180, in errors
self.full_clean()
File "/usr/local/lib/python3.8/site-packages/django/forms/forms.py", line 383, in full_clean
self._post_clean()
File "/usr/local/lib/python3.8/site-packages/django/forms/models.py", line 403, in _post_clean
self.instance.full_clean(exclude=exclude, validate_unique=False)
File "/usr/local/lib/python3.8/site-packages/django/db/models/base.py", line 1188, in full_clean
self.clean()
File "./schema/models.py", line 1439, in clean
if self.metering_modes is True:
File "/usr/local/lib/python3.8/site-packages/django/db/models/fields/related_descriptors.py", line 527, in __get__
return self.related_manager_cls(instance)
File "/usr/local/lib/python3.8/site-packages/django/db/models/fields/related_descriptors.py", line 838, in __init__
raise ValueError('"%r" needs to have a value for field "%s" before '
ValueError: "<CameraModel: Canon Canonflex R2000>" needs to have a value for field "id" before this many-to-many relationship can be used.
So I checked out the docs and it seems like I need to override the save method in the ModelForm. So I did this:
class CameraModelForm(ModelForm):
class Meta:
model = CameraModel
fields = '__all__'
def save(self, commit=True):
form = super(CameraModelForm, self).save(commit=False)
if commit:
form.save(commit=False)
form.save_m2m()
return form
and instead I'm getting a different error:
TypeError at /cameramodel/create/
save() got an unexpected keyword argument 'commit'
Request Method: POST
Request URL: http://localhost:8000/cameramodel/create/
Django Version: 2.2.12
Exception Type: TypeError
Exception Value:
save() got an unexpected keyword argument 'commit'
Exception Location: /home/jonathan/git/camerahub/schema/models.py in save, line 1397
and models.py:1397 is actually an overridden Model save to add a slug field:
class CameraModel(models.Model):
# other stuff here
def save(self, *args, **kwargs):
if not self.slug:
custom_slugify_unique = UniqueSlugify(
unique_check=cameramodel_check, to_lower=True)
self.slug = custom_slugify_unique("{} {} {}".format(
self.manufacturer.name, self.model, str(self.disambiguation or '')))
super().save(*args, **kwargs)
For what it's worth, I am using many-to-many fields in some other models and I'm not seeing a problem with them. I'm a Python/Django beginner though and I'm pretty stuck on this one because simply following the advice on a million other posts like this one hasn't helped me. Grateful for any advice or code snippets anyone can offer.
If anyone needs more context, the whole project is open source and this branch is available here: https://github.com/djjudas21/camerahub/tree/278c_m2m_error
If you override, the save method, it means you want to make some checks/changes before saving your form. I would instead do:
def save(self):
form = super(CameraModelForm, self).save(commit=False)
if ...: # some condition on form values
form.save() # remove the commit here
form.save_m2m()
return form

Django REST framework - MultiSelectField - TypeError: Object of type 'set' is not JSON serializable

I'm trying to make a multi-choice rest api with Django REST framework and django-multiselectfield.
Currently inside the model I have:
ANIMAL = (
('dog', 'Dog'),
('cat', 'Cat'),
)
class MyForm(models.Model):
...
animals = MultiSelectField(choices=ANIMAL)
and in my serializer I have:
class MyFormSerializer(serializers.ModelSerializer):
class Meta:
model = MyForm
fields = (..., 'animals')
animals = fields.MultipleChoiceField(choices=ANIMAL)
When I'm trying to POST into the api using this kind of body:
{
...
"animals": ["cat"],
...
}
I get an error: TypeError: Object of type 'set' is not JSON serializable
Traceback (most recent call last):
File "C:\Python36\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
response = get_response(request)
File "C:\Python36\lib\site-packages\django\core\handlers\base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Python36\lib\site-packages\django\core\handlers\base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Python36\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "c:\mysite\myserver\myform\views.py", line 15, in angels_add
return JsonResponse(serializer.data, status=201)
File "C:\Python36\lib\site-packages\django\http\response.py", line 558, in __init__
data = json.dumps(data, cls=encoder, **json_dumps_params)
File "C:\Python36\lib\json\__init__.py", line 238, in dumps
**kw).encode(obj)
File "C:\Python36\lib\json\encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "C:\Python36\lib\json\encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "C:\Python36\lib\site-packages\django\core\serializers\json.py", line 104, in default
return super().default(o)
File "C:\Python36\lib\json\encoder.py", line 180, in default
o.__class__.__name__)
TypeError: Object of type 'set' is not JSON serializable
Though, the form is submitted and I can see the entire data in the admin panel succesfully (?).
I'm using the following versions:
Django==2.2.1
djangorestframework==3.9.3
django-multiselectfield==0.1.8
any idea why I get this exception?
also, I can switch from multiselectfield to another technology if something else would work and will allow me to add multiple choice fields which I can filter later from the admin panel
class MyFormAdmin(admin.ModelAdmin):
list_filter = (
'animals',
...
)
I've read about ArrayField, but I'm not happy with a solution that fit only one kind of db (postgres) as I might use another.
Thanks in advance,
Etay.
From the source code, the to_representation() method of MultipleChoiceField returns data as set
Create a custom MultipleChoiceField class and use it in your serializer
class CustomMultipleChoiceField(fields.MultipleChoiceField):
def to_representation(self, value):
return list(super().to_representation(value))
class MyFormSerializer(serializers.ModelSerializer):
class Meta:
model = MyForm
fields = (..., 'animals')
animals = CustomMultipleChoiceField(choices=ANIMAL)
JPG's solution might work in some cases, but is not really rugged. (also depends on your use case)
A more low level way of solving this, is by extending the JSONEncoder or even better DjangoJSONEncoder (which is already extended) and using it. This way, you only have to implement it ones.
class CustomJSONEncoder(DjangoJSONEncoder):
"""
Custom encoder that also supports `set`, since we require this for
saving the `MultipleChoiceField`.
"""
def default(self, obj):
if isinstance(obj, set):
return list(obj)
return super().default(obj)

django can't serialize non-model on post

New to django and trying to send an list of id's to the server to update some information. I do not want them to be a model class, theres no need for it. What I am trying to do is put them into a serializer to make sure they are "clean". Here is my code:
View Class:
class Update_Cards(APIView):
# This seems necessary or it will throw an error
queryset = Card.objects.all()
def post(self, request, board_id, format=None):
print request.DATA
serializer = CardMoveSerializer(data=request.DATA, many=True)
#this throws an error
print serializer.data
return Response(serializer.data)
Serializer:
class CardMoveSerializer(serializers.Serializer):
card_id = serializers.IntegerField()
lane_id = serializers.IntegerField()
Error I get:
[{u'lane_id': 21, u'card_id': 3}] #this is to show the data is coming across the wire
Internal Server Error: /api/board/2/updateCards
Traceback (most recent call last):
File "/Library/Python/2.7/site-packages/django/core/handlers/base.py", line 115, in get_response
response = callback(request, *callback_args, **callback_kwargs)
File "/Library/Python/2.7/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/Library/Python/2.7/site-packages/django/views/decorators/csrf.py", line 77, in wrapped_view
return view_func(*args, **kwargs)
File "/Library/Python/2.7/site-packages/rest_framework/views.py", line 327, in dispatch
response = self.handle_exception(exc)
File "/Library/Python/2.7/site-packages/rest_framework/views.py", line 324, in dispatch
response = handler(request, *args, **kwargs)
File "/Users/crob/Documents/workspace/tlckanban/python/rest/views.py", line 37, in post
print card_moves.data
File "/Library/Python/2.7/site-packages/rest_framework/serializers.py", line 499, in data
self._data = [self.to_native(item) for item in obj]
TypeError: 'NoneType' object is not iterable
What I have done is implemented this with a simplejson parser for now, but I feel like its not the best way to do it:
def update_cards(request, board_id):
json_data = simplejson.loads(request.body)
for moveIndex in range(0, len(json_data)):
#do some work
return JSONResponse(json_data, status=status.HTTP_200_OK)
Thanks for the help in advance!
You need to be accessing 'serializer.is_valid()' before accessing the data. Looks like there's a missing bit of API there - serializer.data should probably raise an expection if its accessed before validation.
Seems you are not using Django's serializers but anyway serialization is not made for validation. Use forms to validate your data then use json as you do to serialize it. Django's serializers are for querysets and models only.

Categories

Resources