update the same group if group name clashes - python

How can i update the same group if the name of group user wants to create matches with already created group? If i want to update instead of showing error where should i work on? Is it on validate function or create function?
Here is my serializer
class DeviceGroupSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
devices = DeviceIdSerializer(many=True)
class Meta:
model = DeviceGroup
fields = ['id','name', 'devices',]
def validate(self, data):
errors = {}
try:
name = data['name']
if not bool(name):
#empty or null
errors['name'] = 'Name cannot be empty'
except KeyError:
if not (self.instance and bool(self.instance.name)):
errors['name'] = 'Name is required'
if len(data.get('devices', [])) == 0:
errors['devices'] = 'Device(s) should be specified.'
if bool(errors):
raise serializers.ValidationError(errors)
return data
def create(self, validated_data):
# for create - there is always name; we have already checked that in validation
# TODO Further check for group-name clash - if yes, update the same group
owner = validated_data['owner']
name = validated_data['name']
group = DeviceGroup.objects.create(owner=owner, name=name)
tokens = [d['token'] for d in validated_data['devices'] ]
BaseDevice.objects.filter(token__in=tokens, owner=owner).update(group=group)
return group
def update(self, instance, validated_data):
# for update - there may or may not be name
# if it does, it refers rename
owner = validated_data['owner']
name = validated_data.get('name', None)
if not name is None:
instance.update(name=name)
tokens = [d['token'] for d in validated_data['devices'] ]
BaseDevice.objects.filter(token__in=tokens, owner=owner).update(group=instance)
return instance

You want update_or_create():
A convenience method for updating an object with the given kwargs,
creating a new one if necessary. The defaults is a dictionary of
(field, value) pairs used to update the object.
Based on what you've shared, this would look something like the following, assuming you want to update the owner on DeviceGroup, if a DeviceGroup with the given name already exists:
def create(self, validated_data):
# for create - there is always name; we have already checked that in validation
# TODO Further check for group-name clash - if yes, update the same group
owner = validated_data['owner']
name = validated_data['name']
# created is a boolean telling us if a new DeviceGroup was created
group, created = DeviceGroup.objects.update_or_create(name=name, defaults={'owner': owner})
tokens = [d['token'] for d in validated_data['devices'] ]
BaseDevice.objects.filter(token__in=tokens, owner=owner).update(group=group)
return group

Related

Django keep one element by group and then use property on this queryset

I have a model like this one:
class Extension(models.Model):
name = models.CharField(max_length=32)
version = models.CharField(max_length=16)
#property
def path(self):
return something
I would like to get an Extension queryset containing only the last version for each name and then get a tuple (name, path) for each of these last recent Extension.
I tried
latests = Extension.objects.values('name').annotate(last=Max('version'))
but it gave me a dict without the properties access
How can i do that?
here's one way you could do it:
latests = Extension.objects.values('name').annotate(Max('version'))
q_statement = Q()
for pair in latests:
q_statement |= (Q(name=pair['name']) & Q(version=pair['version__max']))
extensions = Extension.objects.filter(q_statement)

Odoo - add custom field attribute?

Is there a way to add custom field attribute in Odoo? For example every field has attribute help where you can enter message explaining the field for the user. So I want to add custom attribute, so that would change the way field acts for all types of fields.
I want to add into Field class, so all fields would get that attribute. But it seems no matter what I do, Odoo does not see that such attribute was added.
If I simply add new custom attribute like:
some_field = fields.Char(custom_att="hello")
Then it is simply ignored. And I need it to be picked up by method fields_get, which can return wanted attribute value (info what it does:
def fields_get(self, cr, user, allfields=None, context=None, write_access=True, attributes=None):
""" fields_get([fields][, attributes])
Return the definition of each field.
The returned value is a dictionary (indiced by field name) of
dictionaries. The _inherits'd fields are included. The string, help,
and selection (if present) attributes are translated.
:param allfields: list of fields to document, all if empty or not provided
:param attributes: list of description attributes to return for each field, all if empty or not provided
"""
So calling it, does not return my custom attribute (it does return the ones originally defined by Odoo though).
I also tried updating _slots (with monkey patch or just testing by changing source code) attribute in Field class, but it seems it is not enough. Because my attribute is still ignored.
from openerp import fields
original_slots = fields.Field._slots
_slots = original_slots
_slots['custom_att'] = None
fields.Field._slots = _slots
Does anyone know how to properly add new custom attribute for field?
Assuming v9
The result of fields_get is a summary of fields defined on a model, the code shows that it will only add the attribute if the description was filled. It will fetch the description of the current field by calling field.get_description
So in order to ensure that your attribute gets inserted into this self.description_attrs you will need to add an attribute or method that starts with _description_customatt (customatt part from your example) and will return the required data.
I've not run any tests for this but you can look at the code for the fields and their attributes what they actually return. For instance the help attribute description (src)
def _description_help(self, env):
if self.help and env.lang:
model_name = self.base_field.model_name
field_help = env['ir.translation'].get_field_help(model_name)
return field_help.get(self.name) or self.help
return self.help
This is only something you can do if you run OpenERP/ODOO on your own server (in other words, not the cloud version whose code you cannot access).
You will need to modify the <base>/osv/fields.py file and add your changes to the field_to_dict function towards the bottom of the file (the base _column class already saves extra keyword arguments for you -- at least in version 7.0):
def field_to_dict(model, cr, user, field, context=None):
res = {'type': field._type}
...
...
for arg in ('string', 'readonly', ...) :
Somewhere in that long list of attributes you need to insert the name of the one you are interested in.
Alternatively, you could update _column.__init__ to save the names of the extra arguments, and field_to_dict to include them (untested):
diff -r a30d30db3cd9 osv/fields.py
--- a/osv/fields.py Thu Jun 09 17:18:29 2016 -0700
+++ b/osv/fields.py Mon Jun 13 18:11:26 2016 -0700
## -116,23 +116,24 ## class _column(object):
self._context = context
self.write = False
self.read = False
self.view_load = 0
self.select = select
self.manual = manual
self.selectable = True
self.group_operator = args.get('group_operator', False)
self.groups = False # CSV list of ext IDs of groups that can access this field
self.deprecated = False # Optional deprecation warning
- for a in args:
- if args[a]:
- setattr(self, a, args[a])
+ self._user_args = ()
+ for name, value in args:
+ setattr(self, name, value or False)
+ self._user_args += name
def restart(self):
pass
def set(self, cr, obj, id, name, value, user=None, context=None):
cr.execute('update '+obj._table+' set '+name+'='+self._symbol_set[0]+' where id=%s', (self._symbol_set[1](value), id))
def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None):
raise Exception(_('undefined get method !'))
## -1559,20 +1560,22 ## def field_to_dict(model, cr, user, field
res['o2m_order'] = field._order or False
if isinstance(field, many2many):
(table, col1, col2) = field._sql_names(model)
res['m2m_join_columns'] = [col1, col2]
res['m2m_join_table'] = table
for arg in ('string', 'readonly', 'states', 'size', 'group_operator', 'required',
'change_default', 'translate', 'help', 'select', 'selectable', 'groups',
'deprecated', 'digits', 'invisible', 'filters'):
if getattr(field, arg, None):
res[arg] = getattr(field, arg)
+ for arg in field._user_args:
+ res[arg] = getattr(field, arg)
if hasattr(field, 'selection'):
if isinstance(field.selection, (tuple, list)):
res['selection'] = field.selection
else:
# call the 'dynamic selection' function
res['selection'] = field.selection(model, cr, user, context)
if res['type'] in ('one2many', 'many2many', 'many2one'):
res['relation'] = field._obj
res['domain'] = field._domain(model) if callable(field._domain) else field._domain

python adding a class object to a list

Im trying to create an empty instance of my class object and add it to a list, however, as soon as i try to create the object add new data i get an error for it as seen below:
error
Traceback (most recent call last):
File "pagerduty.py", line 96, in <module>
UserData = User()
TypeError: 'dict' object is not callable
code
class User(object):
__attrs = ['Policy','Level', 'StartDate', 'EndDate', 'StartTime',
'EndTime', 'Name', 'Mobile']
def __init__(self, **kwargs):
for attr in self.__attrs:
setattr(self, attr, kwargs.get(attr, None))
def __repr__(self):
return ', '.join(
['%s: %r' % (attr, getattr(self, attr)) for attr in self.__attrs])
OnCallData = []
for User in objPolicyData['users']:
UserData = User()
UserData.Name = User['name']
UserData.Mobile = UserMobile = getUserMobile(User['id'])
for OnCall in User['on_call']:
UserPolicy = OnCall['escalation_policy']
PolicyName = UserPolicy['name']
if PolicyName.lower().find('test') == -1:
UserData.Policy = PolicyName
UserData.Level = OnCall['level']
UserData.StartDate = getDate(OnCall['start'])
UserData.EndDate = getDate(OnCall['end'])
UserData.StartTime = getTime(OnCall['start'])
UserData.EndTime = getTime(OnCall['end'])
OnCallData.append(UserData)
in your for scope, the User identifier is the iterated value from objPolicyData['users'] (as you used it in UserData.Name = User['name'])
you need to use a diffierent name for the iteration.
something like that:
for userI in objPolicyData['users']:
UserData = User()
UserData.Name = userI['name']
UserData.Mobile = UserMobile = getUserMobile(userI['id'])
for OnCall in userI['on_call']:
UserPolicy = OnCall['escalation_policy']
PolicyName = UserPolicy['name']
if PolicyName.lower().find('test') == -1:
UserData.Policy = PolicyName
UserData.Level = OnCall['level']
UserData.StartDate = getDate(OnCall['start'])
UserData.EndDate = getDate(OnCall['end'])
UserData.StartTime = getTime(OnCall['start'])
UserData.EndTime = getTime(OnCall['end'])
Note that using the conventions could prevent this bug for you. meaning, starting the name of a variable with lower case letter (user) and a class with capital letter (User).
this is relevant for most of your variables names
Both your class and the variable you are iterating over is called User. Change one of them to a different name.
By convention in python variable names are lowercase. So I suggest the following change:
for user in objPolicyData['users']:
user_data = User()
user_data.name = User['name']
user_data.mobile = UserMobile = getUserMobile(User['id'])
for on_call in User['on_call']:
user_policy = on_call['escalation_policy']
policy_name = user_policy['name']
if policy_name.lower().find('test') == -1:
user_data.policy = policy_name
user_data.level = on_call['level']
user_data.start_date = get_date(on_call['start'])
and so on. This follows the naming conventions in pep8. This will also take care of your error.
Of course if there is already an established style guide you should follow it and decide for a different name of User (the iteration variable).

Django request not available in form clean

I can't access request from the clean field of a ModelForm. I want to check that the request user is not trying to attend too many events.
I've tried this fix: Django: Accessing request in forms.py clean function
...among other things but am still struggling.
My form is this:
class EventEditForm(forms.ModelForm):
class Meta:
model = Event
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(EventEditForm, self).__init__(*args, **kwargs)
name = forms.CharField(max_length=1024,
initial="Give short, descriptive name",
widget=forms.TextInput(attrs={'onfocus':'if(this.value=="Give short, descriptive name") this.value="";'}))
location = forms.CharField(max_length=1024,
initial="Be specific, give online map link",
widget=forms.TextInput(attrs={'onfocus':'if(this.value=="Be specific, give online map link") this.value="";'}))
dateTimeOptions = {
'format': 'dd/mm/yyyy HH:ii P',
'autoclose': 'true',
'showMeridian': 'true',
}
date = forms.DateTimeField(label="Date and time",widget=DateTimeWidget(options=dateTimeOptions,
attrs={'id':"date-time"}))
host_act = forms.CharField(max_length=1024,
initial="What act will you bring, if any?", required=False,
widget=forms.TextInput(attrs={'onfocus':'if(this.value=="What act will you bring, if any?") this.value="";'}))
description = forms.CharField(required=False, max_length=10240,
initial="Give some details. Any activities prepared?",
widget=forms.Textarea(attrs={'onfocus':'if(this.value=="Give some details. Any activities prepared?") this.value="";'}))
def clean_location(self):
cd = self.cleaned_data
location = cd.get('location')
if location == "Be specific, give online map link" or '':
raise forms.ValidationError("Please enter a location")
return location
def clean_name(self):
cd = self.cleaned_data
name = cd.get('name')
other_names = Event.objects.proposed(datetime.now)
if name == "Give short, descriptive name" or '':
raise forms.ValidationError("Please enter a name")
return name
def clean(self):
"""
Check that there is not another event on at
the same time and place. Then check that user has not committed
to another event on the same date, even somewhere else.
"""
cleaned_data = super(EventEditForm, self).clean()
event_start_estimate = cleaned_data.get("date") - timedelta(hours=3)
event_end_estimate = event_start_estimate + timedelta(hours=7)
location = cleaned_data.get("location")
events_on_date = Event.objects.\
filter(date__range=[event_start_estimate,event_end_estimate])
events_at_location_on_date = events_on_date.filter(location=location)
print events_at_location_on_date
# Check event clash is not this event clashing with itself
try:
events_with_same_date_and_location_id = events_at_location_on_date[0].id
except:
pass
this_event_id = self.instance.id
try:
if events_with_same_date_and_location_id == this_event_id:
events_at_location_on_date = False
except:
pass
if events_at_location_on_date:
raise forms.ValidationError("There is already an event on \
this date. Please join this event or pick another \
date and time. Note: You may have accidentally failed to \
change the date with the picker.")
print self.request
user = self.request
print user
events_on_date_user_has_commit = events_on_date.filter(host__exact=user)
if events_on_date_user_has_commit:
raise forms.ValidationError("You are already committed to an event\
on this date.")
return cleaned_data
My view is:
class EventEditView(UpdateView):
template_name = "edit_event.html"
model=Event
pk_url_kwarg='event_id'
form_class = EventEditForm
You cannot access the request in a form. You need to pass it in from the view in some way. If you're using a FormView generic view, the answer you have linked to is a great way to do it. Looks like you are already expecting the request to be sent in:
self.request = kwargs.pop('request', None)
If you are using a function based view, or a non FormView classed based view, just pass in the request as a keyword argument when you initialize the form. For example:
form = EventEditForm(request.POST, request=request) # or self.request for a class based view

Compare old and updated fields in Django model

I want to compare old and updated field in model. I have did this issue for one field but i want do this for all fields:
class MyUser(User)
def save(self, **kwargs):
if self.pk is not None:
orig = MyUser.objects.get(pk=self.pk)
orig_field_names = orig._meta.get_all_field_names()
field_names = self._meta.get_all_field_names()
# I want do this in loop
if orig.first_name != self.first_name:
print 'first_name changed'
UpdateLog.objects.create(
user = orig,
filed_name = self.first_name,
update_time = datetime.now()
)
super(MyUser, self).save(**kwargs)
Thanks in advance
Here's my go-to function for comparing fields. Gets a little hairy when dealing with foreign keys, but it's not too bad overall:
def get_changes_between_objects(object1, object2, excludes=[]):
"""
Finds the changes between the common fields on two objects
:param object1: The first object
:param object2: The second object
:param excludes: A list of field names to exclude
"""
changes = {}
# For every field in the model
for field in object1._meta.fields:
# Don't process excluded fields or automatically updating fields
if not field.name in excludes and not isinstance(field, fields.AutoField):
# If the field isn't a related field (i.e. a foreign key)..
if not isinstance(field, fields.related.RelatedField):
old_val = field.value_from_object(object1)
new_val = field.value_from_object(object2)
# If the old value doesn't equal the new value, and they're
# not both equivalent to null (i.e. None and "")
if old_val != new_val and not(not old_val and not new_val):
changes[field.verbose_name] = (old_val, new_val)
# If the field is a related field..
elif isinstance(field, fields.related.RelatedField):
if field.value_from_object(object1) != field.value_from_object(object2):
old_pk = field.value_from_object(object1)
try:
old_val = field.related.parent_model.objects.get(pk=old_pk)
except field.related.parent_model.DoesNotExist:
old_val = None
new_pk = field.value_from_object(object2)
try:
new_val = field.related.parent_model.objects.get(pk=new_pk)
except field.related.parent_model.DoesNotExist:
new_val = None
changes[field.verbose_name] = (old_val, new_val)
return changes
Usage:
>>> item = Item.objects.get(pk=1)
>>> item_old = Item.objects.get(pk=1)
>>> print item.my_attribute
'foo'
>>> item.my_attribute = 'bar'
>>> get_changes_between_objects(item, item_old)
{'My Attribute': ('bar', 'foo')}
You want a signal. For quick reference, here's the introductory paragraph or so from that link:
Django includes a “signal dispatcher” which helps decoupled
applications get notified when actions occur elsewhere in the
framework. In a nutshell, signals allow certain senders to notify a
set of receivers that some action has taken place. They’re especially
useful when many pieces of code may be interested in the same events.
Django provides a set of built-in signals that let user code get
notified by Django itself of certain actions.
Read the docs before vote -1. Catch the signal is the better way to do this thing

Categories