I have a model, User, in which the primary unique field is email. I have a separate Organization model which allows users to map to organizations through a many-to-many mapping.
I want the serializer to allow users to be created if they have an existing email but no organization association (organization is taken from the user making the request so is not in the payload).
The standard ModelSerializer includes a check against the field in to_internal_value(). I am consequently trying to override it like so:
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
"""
fields = self._writable_fields
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:
if validate_method == self.validate_email:
if User.objects.filter(email=primitive_value).exists():
if not self.active_organization.members.filter(email=primitive_value).exists():
continue
else:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
return super().to_internal_value(data)
This works, but the error that is returned if the object already has a record in User and Organization does not correctly map as a dictionary. For example, the validation error shows this:
[ErrorDetail(string='User with this Email already exists.', code='unique')]
Instead of what it should show, this:
{'email': [ErrorDetail(string='User with this Email already exists.', code='unique')]}
I tested by overriding the method and trying both my custom called code vs. the original, and it replicates the above findings:
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
"""
try:
print('trying custom')
for field in self._writable_fields:
if field.field_name == 'email':
print(field.run_validation)
validated_value = field.run_validation(field.get_value(data))
except Exception as e:
print('error custom')
print(str(e))
try:
print('Trying original')
value = super().to_internal_value(data)
except Exception as e:
print('Exception - original')
print(str(e))
Output:
trying custom
<bound method CharField.run_validation of EmailField(max_length=254, validators=[<UniqueValidator(queryset=User.objects.all())>])>
error custom
[ErrorDetail(string='User with this Email already exists.', code='unique')]
Trying original
Exception - original
{'email': [ErrorDetail(string='User with this Email already exists.', code='unique')]}
Can anyone help me understand why this is happening please? I'm really stuck as to how this is happening.
I was not able to figure out why exactly the errors generated by to_internal_value(self, data) change when I copy the code from the underlying ModelSerializer vs calling super(). However, I did find a kind-of hackish workaround.
Because I strip out all the other related info (nested serializers, kind of) before hand, I'm really just worried about the email. Therefore, I traverse the _writable_fields twice: the first time I look for an existing email where it isn't in the organization. If those conditions are true, I return a dictionary and proceed to the create() method. If they fail, I call super. So far this seems to work. Here is my simplified class.
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
"""
try:
for field in self._writable_fields:
if field.field_name == 'email':
primitive_value = field.get_value(data)
validator = EmailValidator()
validator(primitive_value)
if User.objects.filter(email=primitive_value).exists():
if not self.active_organization.members.filter(email=primitive_value).exists():
self.email_already_exists = True
return {'email': primitive_value}
except:
pass
return super().to_internal_value(data)
For reference, this is the complete ViewSet and Serializer I'm using:
class AdminUsersSerializer(serializers.ModelSerializer):
"""User serializer for the admin user view"""
groups = GenericGroupSerializer(source='group_set', many=True, required=False)
permission_sets = GenericPermissionSetSerializer(source='permissionset_set', many=True, required=False)
active_organization = None
email_already_exists = False
class Meta:
model = User
fields = ['pk', 'email', 'groups', 'permission_sets'] # , 'permissions']
def set_active_organization(self, organization):
self.active_organization = organization
def create(self, validated_data):
if not self.email_already_exists:
user = User.objects.create(
email=validated_data['email']
)
user.set_password(None)
user.save()
else:
user = User.objects.get(email=validated_data['email'])
return user
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
"""
try:
for field in self._writable_fields:
if field.field_name == 'email':
primitive_value = field.get_value(data)
validator = EmailValidator()
validator(primitive_value)
if User.objects.filter(email=primitive_value).exists():
if not self.active_organization.members.filter(email=primitive_value).exists():
self.email_already_exists = True
return {'email': primitive_value}
except:
pass
return super().to_internal_value(data)
class AdminUsersViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = AdminUsersSerializer
permission_classes = [account_permissions.IsAdminRequired]
http_method_names = ['get', 'post']
active_organization = None
def create(self, request, *args, **kwargs):
self.active_organization = self.request.user.activeorganization.organization
groups = None
permission_sets = None
serializer = self.get_serializer(data=request.data)
serializer.set_active_organization(self.active_organization)
if 'groups' in serializer.initial_data:
if serializer.initial_data.get('groups'):
groups = serializer.initial_data.pop('groups')
else:
serializer.initial_data.pop('groups')
if 'permission_sets' in serializer.initial_data:
if serializer.initial_data.get('permission_sets'):
permission_sets = serializer.initial_data.pop('permission_sets')
else:
serializer.initial_data.pop('permission_sets')
# Normal method functions
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
# Created user
user = User.objects.get(email=serializer.data['email'])
# Add user to organization
self.active_organization.members.add(user)
# Make sure to format the ajax groups data properly
if groups:
try:
group = Group.objects.get(
id=groups,
organization=self.active_organization
)
group.members.add(user)
except:
pass
if permission_sets:
try:
permission_set = PermissionSet.objects.get(
id=permission_sets,
organization=self.active_organization
)
permission_set.members.add(user)
except:
pass
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def get_queryset(self):
return self.queryset.filter(
userorganizationmembership__organization=self.request.user.activeorganization.organization
).all()
It does feel hackish, but it seems to be getting the job done.
Related
I have a collection of ever more specialized classes which correspond to collections of the same kind of data (temperature, density, etc) but for different drifts, for example, one subclass has dimensions (nx, ny) and a different suclass has dimensions (ncv), and I want to reflect that in the docstrings, for having a better documentation using Sphinx.
After reading many very useful threads here in Stack Overflow, I have arrived to this model:
import numpy as np
from functools import wraps
def class_decorator(cls):
import ipdb; ipdb.set_trace()
clsdict = {}
mro = cls.mro()
mro.reverse()
for tmp in mro[1:]: ##Ignore object class parent.
clsdict.update(tmp.__dict__)
for name, method in clsdict.items():
if hasattr(method, '__og_doc__'):
try:
method.__doc__ = method.__og_doc__.format(**clsdict)
except:
pass
else:
try:
method.__og_doc__ = method.__doc__
method.__doc__ = method.__doc__.format(**clsdict)
except:
pass
return cls
def mark_documentation(fn):
if not hasattr(fn, '__og_doc__'):
try:
fn.__og_doc__ = fn.__doc__
except:
pass
#wraps(fn)
def wrapped(*args, **kwargs):
return fn(*args, **kwargs)
return wrapped
def documented_property(fn):
if not hasattr(fn, '__og_doc__'):
try:
fn.__og_doc__ = fn.__doc__
except:
pass
#wraps(fn)
def wrapped(*args, **kwargs):
return fn(*args, **kwargs)
prp= property(wrapped)
prp.__og_doc__ = fn.__og_doc__
return prp
#class_decorator
class Base(object):
_GRID_DIM = 'nx, ny'
_TYPE = 'BaseData'
def __init__(self, name):
self.name = name
def shape(self):
""" This docstring contains the type '{_TYPE}' of class."""
print('Simple')
def operation(self, a, b, oper=np.sum, **kwargs):
""" Test for functions with args and kwargs in {_TYPE}"""
return oper([a,b])
#classmethod
def help(cls, var):
try:
print(get(cls, var).__doc__)
except:
print("No docstring yet.")
#class_decorator
class Advanced(Base):
_GRID_DIM = 'ncv'
_TYPE = 'AdvancedData'
def __init__(self,name):
super().__init__(name)
#property
#mark_documentation
# #documented_property
def arkansas(self):
"""({_GRID_DIM}, ns): Size of Arkansaw."""
return 'Yeah'
I am aiming to get the correctly formatted docstring when I call the help method or I use Sphinx, so that:
> adv = Advanced('ADV')
> adv.help("arkansas")
(ncv, ns): Size of Arkansaw.
> adv.help("operation")
Test for functions with args and kwargs in AdvancedData
I have managed to make it work so far, except for properties, because I assigned __og_doc__ to the function, but the property does not have that attribute. My last attempt at monkeypatching this, documented_property, fails because property is inmutable (as expected), and I cannot come up with any way to avoid this roadblock.
Is there any way around this problem?
My class Test below can have multiple functions, in this example, one of the functions is put_in_db(). My class expects a JSON object that will get converted to the dictionary and will be passed to invoke_func. Now, within invoke_func, how can I invoke different functions using the key functional_call and the parameters list using params. I have chosen params as a list so that the order is maintained. I cannot change put_in_db() function since this can be from an external class too.
class Test:
def __init__(self):
print("Test initialized")
def put_in_db(name, age):
print("put in db")
def invoke_func(self, my_dict):
print(my_dict)
function_call = my_dict.get('functional_call')
params = my_dict.get('params')
print(params)
'''
how to invoke put_in_db from here using function_call
and params
'''
if __name__ == "__main__":
T = Test()
data = {'functional_call': 'put_in_db', 'params': [{'name': 'Saf', 'age': '81'}]}
T.invoke_func(data)
You can use exec():
def invoke_func(self, my_dict):
print(my_dict)
function_call = my_dict.get('functional_call')
params = my_dict.get('params')
print(params)
exec(f'{function_call}({params})')
Note: exec() does have security vulnerabilities, as it will execute any code that is passed to it, so exercise caution when the user inputs something that is then executed.
class Test:
def __init__(self):
print("Test initialized")
def put_in_db(self,name, age):
print(f"put {name}, age {age}, in db")
def invoke_func(self, my_dict):
print(my_dict)
function_call = my_dict.get('functional_call')
params = my_dict.get('params')
print(params)
getattr(self,function_call)(**params)
if __name__ == "__main__":
T = Test()
data = {'functional_call': 'put_in_db', 'params': {'name': 'Saf', 'age': '81'}}
T.invoke_func(data)
getattr(some_object, att_name) will grab the attribute att_name from some_object, assuming it exists (if not it will give an AttributeError unless you also include a default). Then you can just invoke it by unpacking params as an argument.
I made two other changes to your code:
You left out self from your put_in_db method.
You made param a list containing a dict, but it's easier to work with if you just make it a dict.
You will probably want to add some error checks error checks for if/when someone passes in an string that doesn't correspond to an attribute.
There is an error in my code "'Request' object has no attribute 'learner'". Here, My requirement is "request.learner" is empty then call "CDetailsSerializer" otherwise call "CourseDetailsSerializer".
def list(self, request, **kwargs):
try:
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
if not request.learner:
serializer = CDetailsSerializer(queryset, many=True)
else:
serializer = CourseDetailsSerializer(queryset, many=True)
print('s', serializer.data)
response_data = {'course': serializer.data}
return self.jp_response(s_code='HTTP_200_OK', data=response_data)
except Exception as e:
print(e)
return self.jp_error_response('HTTP_500_INTERNAL_SERVER_ERROR', 'EXCEPTION', [str(e), ])
Here always calling (else part) CourseDetailsSerializer, but in some situations, I also want to call (if part) CDetailsSerializer.Give me a solution to fix this.
I am new to python and am trying to learn OOP. I have this mock up quiz that i have been trying to solve. So far am able to pass 5 test
Here is the challenge
Users come in 3 flavors, normal users, moderators, and admins. Normal users can only create new comments, and edit the their own comments. Moderators have the added ability to delete comments (to remove trolls), while admins have the ability to edit or delete any comment.
Users can log in and out, and we track when they last logged in
Comments
Encapsulation of Properties
All classes should have no publicly accessible fields
You should make sure you at least "hide" the required fields, for example, using _name instead of _name. Alternatively, feel free to use a better solution as extra credit.
The method-based API is provided. These must be completed as-is.
Additional methods are allowed, though remember to keep read-only properties read-only.
Instantiation
Classes should be instantiated with properties (as provided), to create instances with values already assigned.
User/Moderator/Admin defaults:
Should be marked as not logged in
Should return None for the last logged in at property
Comment defaults:
Should set the current timestamp for the created at property upon instantiation
Replied To is optional, and should be None if not provided.
Inheritance & Access Control
User
Users can be logged in and out.
When logging in, set the last_logged_in_at timestamp. Do not modify this timestamp when logging out
Users can only edit their own comments
Users cannot delete any comments
Moderator is a User
Moderators can only edit their own comments
Moderators can delete any comments
Admin is both a User and a Moderator
Admins can edit any comments
Admins can delete any comments
Composition
Comments contain a reference to the User who created it (author)
Comments optionally contain a reference to another comment (replied_to)
When converting to a string (to_string), the following format is used:
No replied to:
message + " by " + author.name
With replied to:
message + " by " + author.name + " (replied to " + repliedTo.author.name + ")"
this is my solution
import datetime
class user:
def __init__(self, name, lastloggedIn = None):
self.name = name
self.loggedIn = False
self.lastloggedIn = None
def name(self):
return self.name
def name(self, value):
self.name = value
def is_logged_in(self):
return self.loggedIn
def last_logged_in_at(self):
return self.lastloggedIn
def log_in(self):
self.loggedIn = True
self.lastloggedIn = datetime.datetime.utcnow()
def log_out(self):
self.loggedIn = False
def can_edit(self, comment):
if comment.author.name == self.name:
return True
else:
return False
def can_delete(self, comment):
return False
# def to_string(self):
# pass
class moderator(user):
def __init__(self, name):
user.__init__(self, name)
def can_delete(self, comment):
return True
class admin(moderator):
def __init__(self, name):
moderator.__init__(self, name)
def can_edit(self, comment):
return True
class comment:
def __init__(self, author, message, replied_to = None, createdAt = None):
self.createdAt = datetime.datetime.now()
self.author = author
self.message = message
self.replied_to = replied_to
def author(self):
return self._author
def author(self, value):
self.author = value
def message(self):
return self.message
def message(self, value):
self.message = value
def created_at(self):
return self.createdAt
def replied_to(self):
return self.replied_to
def replied_to(self, value):
self.replied_to = value
def to_string(self):
if self.replied_to == None:
return self.replied_to + " by " + self.author.name
import unittest
user1 = user('User 1')
mod = moderator('Moderator')
class Test(unittest.TestCase):
def test_instantiation(self):
self.assertEqual(user1.name,'User 1', 'User name is set correctly')
user1.name = 'User 1 Updated'
self.assertEqual(user1.name,'User 1 Updated', 'User name can be updated')
self.assertIsInstance(mod, user, 'Moderator is a user')
Am getting two main errors. the last_logged_in method should return None and the datetime.datetime.now() doesn't seem to be working correctly
AssertionError: <bound method comment.created_at of <solution.comment object at 0x7fd5a21d0668>> is not an instance of <class 'datetime.datetime'>
AssertionError: <bound method user.last_logged_in_at of <[35 chars]ba8>> != None : Last logged in date is not set by default
Let's start with the first error:
AssertionError: <bound method comment.created_at of <solution.comment object at 0x7fd5a21d0668>> is not an instance of <class 'datetime.datetime'>
ITsays you are passing datetime instance. Let's check:
>>> import datetime
>>> datetime.datetime.now()
datetime.datetime(2018, 11, 21, 10, 28, 26, 996940) # this is datetime instance
what you can do is convert it to string and pass it:
>>> datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
'2018-11-21 06:51:22'
Like:
class comment:
def __init__(self, author, message, replied_to = None, createdAt = None):
self.createdAt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
For the second error:
AssertionError: <bound method user.last_logged_in_at of <[35 chars]ba8>> != None : Last logged in date is not set by default
You are not setting lastloggedIn:
def __init__(self, name, lastloggedIn = None):
self.name = name
self.loggedIn = False
self.lastloggedIn = lastloggedIn
and same here you are passing an instance:
>>> datetime.datetime.utcnow()
datetime.datetime(2018, 11, 21, 6, 46, 25, 248409)
change it to return the string instead of an instance:
def log_in(self):
self.loggedIn = True
self.lastloggedIn = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
I am working on a small text adventure in python, and am attempting to use classes. I'm not very well versed in OOP and although I feel like I'm slowly gaining a greater understanding...I know that I still have a ways to go.
This is my room class
#!usr/bin/env python3
"""
A room class and a method to load room data from json files
"""
import json
class Room():
def __init__(
self,
id = "0",
name = "An empty room",
desc = "There is nothing here",
items = {},
exits = {},
):
self.id = id
self.name = name
self.desc = desc
self.items = items
self.exits = exits
def __str__(self):
return "{}\n{}\n{}\n{}".format(self.name, self.desc, self.items, self.exits)
# Create method to verify exits
def _exits(self, dir):
if dir in self.exits:
return self.exits[dir]
else:
return None
def north(self):
return self._exits('n')
def south(self):
return self._exits('s')
def east(self):
return self._exits('e')
def west(self):
return self._exits('w')
# Create method to get room info from json file
def get_room(id):
ret = None
with open("data/{}.json".format(str(id)) , "r") as f:
jsontext = f.read()
d = json.loads(jsontext, strict = False)
d['id'] = id
ret = Room(**d)
return ret
This is my map class
#!/usr/bin/env python3
from rooms import *
"""
Map class used to build a map from all of the rooms
"""
class Map():
def __init__(self, rooms = {}):
self.rooms = rooms
def __str__(self):
return map(str, rooms)
def build_map(id, num_of_rooms):
rooms = {}
room_count = 0
while room_count < num_of_rooms:
rooms[id] = get_room(id)
id += 1
room_count += 1
return rooms
a_map = Map(build_map(1, 3))
def test_map(map):
return map.rooms
print(test_map(a_map))
I'm not understanding why test_map only returns a list of objects, and was wondering how I might be able to receive the actual list of rooms so that I can confirm that they were created properly. I'm sure that I'm just going about this the COMPLETE wrong way...which is why I've come here with the issue.
For general information about __str__ and __repr__, Check out this answer.
In this case, here's what's happening:
Your __str__ function on Map doesn't return a string, it returns a map object. __str__ must return a string.
That's not causing an error because the function isn't getting called, here: test_map returns the given Map's rooms attribute, which is a dictionary. If you tried to do print(a_map) you'd get an exception.
Dictionaries have their own __str__ method, which is getting called here but the dictionary's __str__ method calls __repr__ on its members, which you haven't defined. (See the linked answer for details on why this is so.)
When you haven't defined a __repr__ for your class, you get the __repr__ from object, which is why your print(test_map(a_map)) gives you output like {1: <__main__.Room instance at 0x7fca06a5b6c8>, 2: <__main__.Room instance at 0x7fca06ac3098>, 3: <__main__.Room instance at 0x7fca06ac36c8>}.
What I'd suggest:
Write __repr__ functions for both Map and Room.
Have Map.__repr__ return a string that indirectly relies on Room.__repr__, something like the following:
def __repr__(self):
return '<Map: {}>'.format(self.rooms)
This isn't maybe the best long-term approach - what if you have a map with a hundred rooms? - but it should get you going and you can experiment with what works best for you.