I have a custom .save method on my model. The model has a start_date and a last_edited value. If the difference between these values is more than 14 days, it should copy/clone itself on save (instead of saving).
You probably already see the problem: infinite recursion. If the clone saves itself, the values will still differ 14 days and the whole copy process will start anew.
Therefore I want to to pass these copies a 'copy' argument in their .save parameter, in order to prevent them from triggering the copy process themselves.
To this end I've written the following code:
def save(self, *args, **kwargs):
#check if a submission is older than the 'create a new one' threshold
#Create a new one if that is the case
delta = self.last_edited - self.start_date
print(args)
print(kwargs)
if 'copy' in kwargs or 'copy' not in args:
print('cloning!')
if delta.days >= 14:
clone = self
clone.pk = None
clone.save('copy')
super(AssessmentSubmission, self).save(*args, **kwargs)
However, for some reason clone.save(copy) does not pass the 'copy' variable to the .save method. I even added some print statements to print all args and kwarg arguments, but both return empty lists/ dicts.
Does anyone know what I am doing wrong?
Why don't you set a flag field in the model? Cleaner than *kwargs and **args. Something like:
class AssessmentSubmission(models.Model):
'''
Your other fields here
'''
flag_field = models.IntegerField(default=0,blank=True,null=True)
def save(self):
#check if a submission is older than the 'create a new one' threshold
#Create a new one if that is the case
delta = self.last_edited - self.start_date
print(args)
print(kwargs)
if not self.flag_field:
print('cloning!')
if delta.days >= 14:
clone = self
clone.pk = None
clone.flag_field = 1
clone.save('copy')
super(AssessmentSubmission, self).save()
Related
The task is to write a class decorator, which reads a JSON file and makes its key/values to become properties of the class.
But one of conditions is that there has to be the ability to pass values manually (by creating a class object) as well.
I almost did it. There's just a tiny problem. The program reads data from JSON file and passes them successfully to the class. But when passing values manually during creation of an object of the class, values don't change and they are still being taken from JSON.
The problem only disappears when passing values as default values.
room = Room(1, 1) # Doesn't work
room = Room(tables=1, chairs=1) # Does work
Since arguments have to be passed only as numbers in tests, I have to manage it to work with just numbers, not default values.
Here's the code.
from json import load
def json_read_data(file):
def decorate(cls):
def decorated(*args, **kwargs):
if kwargs == {}:
with open(file) as f:
params = {}
for key, value in load(f).items():
params[key] = value
return cls(**params)
else:
return cls(*args, **kwargs)
return decorated
return decorate
#json_read_data('furniture.json')
class Room:
def __init__(self, tables=None, chairs=None):
self.tables = tables
self.chairs = chairs
def is_it_enough(self):
return self.chairs * 0.5 - self.tables > 0.4
kitchen = Room() # This is passing values from JSON file
print(kitchen.__dict__) # Prints {'tables': 2, 'chairs': 5}
room = Room(tables=1, chairs=1) # This is passing values manually
print(room.__dict__) # Prints {'tables': 1, 'chairs': 1}
'''
JSON file:
{
"tables": 2,
"chairs": 5
}
'''
But if we change to room = Room(1, 1), print(room.dict) prints {'tables': 2, 'chairs': 5} again. Please help me solve this problem!
You need to add your arguments to the decorator. Remember that your decorator is called first and then it calls the decorated function.
You could declare your decorator as: def json_read_data(file, *args): then the subsequent calls to cls() would have to be adapted to accept them. The second one already does, the first one needs modification.
It seems, this edit really worked:
def decorated(*args, **kwargs):
if not args and not kwargs:
For my project I need to dynamically create custom (Class) methods.
I found out it is not so easy in Python:
class UserFilter(django_filters.FilterSet):
'''
This filter is used in the API
'''
# legacy below, this has to be added dynamically
#is_field_type1 = MethodFilter(action='filter_field_type1')
#def filter_field_type1(self, queryset, value):
# return queryset.filter(related_field__field_type1=value)
class Meta:
model = get_user_model()
fields = []
But it is giving me errors (and headaches...). Is this even possible?
I try to make the code between #legacy dynamic
One option to do this I found was to create the class dynamically
def create_filter_dict():
new_dict = {}
for field in list_of_fields:
def func(queryset, value):
_filter = {'stableuser__'+field:value}
return queryset.filter(**_filter)
new_dict.update({'filter_'+field: func})
new_dict.update({'is_'+field: MethodFilter(action='filter_'+field)})
return new_dict
meta_model_dict = {'model': get_user_model(), 'fields':[]}
meta_type = type('Meta',(), meta_model_dict)
filter_dict = create_filter_dict()
filter_dict['Meta'] = meta_type
UserFilter = type('UserFilter', (django_filters.FilterSet,), filter_dict)
However, this is giving me
TypeError at /api/v2/users/
func() takes 2 positional arguments but 3 were given
Does anyone know how to solve this dilemma?
Exception Value: 'UserFilter' object has no attribute 'is_bound'
You are getting this error because the class methods you are generating, are not bound to any class. To bound them to the class, you need to use setattr()
Try this on a console:
class MyClass(object):
pass
#classmethod
def unbound(cls):
print "Now I'm bound to ", cls
print unbound
setattr(MyClass, "bound", unbound)
print MyClass.bound
print MyClass.bound()
Traceback:
UserFilter = type('Foo', (django_filters.FilterSet, ), create_filter_dict().update({'Meta':type('Meta',(), {'model':
get_user_model(), 'fields':[]} )})) TypeError: type() argument 3 must
be dict, not None
Now, this is failing because dict.update() doesn't return the same instance, returns None. That can be fixed easily
class_dict = create_filter_dict()
class_dict.update({'Meta':type('Meta',(), {'model': get_user_model(), 'fields':[]})}
UserFilter = type('Foo', (django_filters.FilterSet, ), class_dict))
However, just look how messy that code looks. I recommend to you to try to be
clearer with the code you write even if it requires to write a few extra lines. In the long run, the code will be easier to maintain for you and your team.
meta_model_dict = {'model': get_user_model(), 'fields':[]}
meta_type = type('Meta',(), meta_model_dict)
filter_dict = create_filter_dict()
filter_dict['Meta'] = meta_type
UserFilter = type('Foo', (django_filters.FilterSet,), filter_dict)
This code might not be perfect but it is more readable than the original line of code you posted:
UserFilter = type('Foo', (django_filters.FilterSet, ), create_filter_dict().update({'Meta':type('Meta',(), {'model': get_user_model(), 'fields':[]})}))
And removes a complication on an already kinda difficult concept to grasp.
You might want to learn about metaclasses. Maybe you can overwrite the new method of a class. I can recommend you 1 or 2 posts about that.
Another option is that maybe you are not adding the filters correctly or in a way django doesn't expect? That would explain why you get no errors but none of your functions gets called.
You can use classmethod. Here is example how you can use it:
class UserFilter:
#classmethod
def filter_field(cls, queryset, value, field = None):
# do somthing
return "{0} ==> {1} {2}".format(field, queryset, value)
#classmethod
def init(cls,list_of_fields ):
for field in list_of_fields:
ff = lambda cls, queryset, value, field=field: cls.filter_field(queryset, value, field )
setattr(cls, 'filter_'+field, classmethod( ff ))
UserFilter.init( ['a','b'] )
print(UserFilter.filter_a(1,2)) # a ==> 1 2
print(UserFilter.filter_b(3,4)) # b ==> 3 4
You are asking for:
custom (Class) methods.
So we take an existing class and derive a subclass where you can add new methods or overwrite the methods of the original existing class (look into the code of the original class for the methods you need) like this:
from universe import World
class NewEarth(World.Earth):
def newDirectionUpsideDown(self,direction):
self.rotationDirection = direction
All the other Methods and features of World.Earth apply to NewEarth only you can now change the direction to make the world turn your new way.
To overwrite an existing method of a class is as as easy as this.
class NewEarth(World.Earth):
def leIitRain(self,amount): # let's assume leIitRain() is a standard-function of our world
return self.asteroidStorm(amount) #let's assume this is possible Method of World.Earth
So if someone likes a cool shower on earth he/she/it or whatever makes room for new development on the toy marble the burning way.
So have fun in your way learning python - and don't start with complicated things.
If I got you completely wrong - you might explain your problem in more detail - so more wise people than me can share their wisdom.
visible = models.BooleanField()
owner = models.ForeignKey(User, null=True)
def update_address(**kwargs):
address = Address.objects.get(address=kwargs.get('address'))
try:
address.visible = kwargs.get('visible')
except:
pass
try:
address.owner = kwargs.get('owner')
except:
pass
update_address() should result in nothing happening to address.visible or address.owner.
update_address(owner=None) should delete whatever existing owner object was set.
The thing that's confusing me is how to tell if owner=None was explicitly set so I know to delete the existing owner object, or if it was called without owner set to anything so I should leave the owner as it is.
you can use the "in" keyword to check if the key is there or you can specify the default param in the second argument of the dict.get(key, default) function
if 'visible' in kwargs:
do something
# OR
visible = kwargs.get('visible', False)
Update:
if your super() class (ie the parent model) doesn't take the visible param, you can use the dict.pop(key, default) to extract the param before passing it to the super. I thought this could be useful for you to know as well.
def __init__(self, *args, **kwargs):
visible = kwargs.pop('visible', False)
super().__init__(*args, **kwargs)
You could use in and keys:
if 'visible' in kwargs.keys():
...
You can also make your own default option, if that is more convenient; by making an instance of object you can ensure it's distinct from anything else
no_parm = object() # this object is only used for default parms
def thisfunc( p = no_parm ):
if p is no_parm:
# default was used
def thatfunc(**kwargs):
rateval = kwargs.get('rate', no_parm)
if rateval is not no_parm:
# it could be None, if the functiion wa called
# as thatfunc( rate=None)
#
...
Note the use of is for comparisons - this is possible - and recommended - since we are checking if we have two references to exactly the same object - and not a value equality as == does.
I'm new to django and I've been developing a simple application for the past month or so, but I have a problem, something I did not manage to do.
I have a simple model called WeeklyPlaylist (from my models.py):
class WeeklyPlaylist(models.Model):
total_num_entries_in_playlist = 8
get_pos_choices = get_integer_choices(1, total_num_entries_in_playlist)
sched = models.ForeignKey(Schedule)
week = models.IntegerField(choices=get_integer_choices(1, 52))
position = models.IntegerField(choices=get_pos_choices)
where 'position' simply indicates the position of a video in a playlist.
I'd like to provide the admin with the ability to swap the position of one video in a playlist with another video in that same playlist, through the change/update form of the model above (from my admin.py):
class WeeklyPlaylistAdmin(admin.ModelAdmin):
(...)
readonly_fields = ('position',)
form = WeeklyPlaylistAdminForm
def get_changelist_form(self, request, obj=None, **kwargs):
return WeeklyPlaylistAdminForm
and I'm defining my own form for this object (still from admin.py):
class WeeklyPlaylistAdminForm(ModelForm):
class Meta:
model = WeeklyPlaylist
fields = ('position',)
swap_with_position = forms.IntegerField(widget=forms.Select(choices=WeeklyPlaylist.get_pos_choices))
def clean_swap_with_position(self):
swap_with_position = self.cleaned_data['swap_with_position']
instance = getattr(self, 'instance', None)
if instance and instance.id and swap_with_position == self.instance.position:
raise forms.ValidationError("You must specify a different position than the actual one.")
# select the database obj to swap position with
other = WeeklyPlaylist.objects.filter(sched__screen__name=self.instance.sched.screen.name, sched__year__exact=self.instance.sched.year, week=self.instance.week, position=swap_with_position)
if other.count() != 1:
raise forms.ValidationError("The desired position does not correspond to any existing WeeklyPlaylist entry.")
return swap_with_position
What I had in my mind basically was to provide an extra 'select' html tag to the admin in the change/update form of the WeeklyPlaylist model where he could enter the new position in the playlist for the current video, with the necessary checks in the associated clean_ method to make sure the desired playlist position is valid.
So far so good. Now, my problem is the following: when the admin clicks on the 'save' button, how can I simultaneously save the modified object, and the one it is exchanging its position with? I've tried doing this in the save() method of the form, using the following code:
def save(self, commit=True, *args, **kwargs):
m = super(WeeklyPlaylistAdminForm, self).save(commit=False, *args, **kwargs)
cleaned_data = self.cleaned_data
swap_with_position = cleaned_data.get("swap_with_position")
if commit:
# select the database obj to swap position with
other = WeeklyPlaylist.objects.get(sched__screen__name=m.sched.screen.name, sched__year__exact=m.sched.year, week=m.week, position=swap_with_position)
m.position, other.position = other.position, m.position
m.save()
other.save()
return m
That seems perfectly fine, except that for some reason, commit is always false, even though 'm' is saved after the operation is finished, which is something I don't understand. But as a consequence, other.save() is never called, and if I remove the if statement checking the value of commit, I won't be able to do a save(commit=False) on the WeeklyPlaylistAdminForm objects, which can be annoying...
So, any suggestions to help me?
Many thanks in advance!
Cheers!
Adrien
When I've done things similar to this in Django I haven't implemented models that are specifically swapping with another category but that they re-sort the list; so moving element with order 5 to order 0 would shift all the previous elements with order in the range from 0 to 4 up, but swapping with a specifically ordered element should be much easier.
I would store the previous position of your model before any modifications take place and detect if it has changed in the save method before the saving is actually done. If it has changed then I would save the current model then do a lookup for the model that did have that position and that doesn't have the current position and do an update on that one to correct the position; hopefully the following code should help demonstrate what I mean:
class WeeklyPlaylist(models.Model):
def __init__(self, *args, **kwargs):
super(WeeklyPlaylist, self).__init__(*args, **kwargs)
self._position = int(self.position)
def save(self, *args, **kwargs):
super(WeeklyPlaylist, self).save(*args, **kwargs)
# position has changed, so change the position of the element that held the new position now held by this element
if int(self.position) != self._position:
WeeklyPlaylist.objects.exclude(pk=self.pk).filter(
position=self.position
).update(position=self._position)
I'm building my first form with django, and I'm seeing some behavior that I really did not expect at all. I defined a form class:
class AssignmentFilterForm(forms.Form):
filters = []
filter = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(forms.Form, self).__init__(*args, **kwargs)
self.filters.append(PatientFilter('All'))
self.filters.append(PatientFilter('Assigned', 'service__isnull', False))
self.filters.append(PatientFilter('Unassigned', 'service__isnull', True))
for i, f in enumerate(self.filters):
self.fields["filter"].choices.append((i, f.name))
When I output this form to a template using:
{{ form.as_p }}
I see the correct choices. However, after refreshing the page, I see the list three times in the select box. Hitting refresh again results in the list showing 10 times in the select box!
Here is my view:
#login_required
def assign_test(request):
pg = PhysicianGroup.objects.get(pk=physician_group)
if request.method == 'POST':
form = AssignmentFilterForm(request.POST)
if form.is_valid():
yes = False
else:
form = AssignmentFilterForm()
patients = pg.allPatients().order_by('bed__room__unit', 'bed__room__order', 'bed__order' )
return render_to_response('hospitalists/assign_test.html', RequestContext(request, {'patients': patients, 'form': form,}))
What am I doing wrong?
Thanks, Pete
This is actually a feature of Python that catches a lot of people.
When you define variables on the class as you have with filters = [] the right half of the expression is evaluated when the class is initially defined. So when your code is first run it will create a new list in memory and return a reference to this list. As a result, each AssignmentFilterForm instance will have its own filters variable, but they will all point to this same list in memory. To solve this just move the initialization of self.filters into your __init__ method.
Most of the time you don't run into this issue because the types you are using aren't stored as a reference. Numbers, booleans, etc are stored as their value. Strings are stored by reference, but strings are immutable meaning a new string must be created in memory every time it is changed and a new reference returned.
Pointers don't present themselves often in scripting language, so it's often confusing at first when they do.
Here's a simple IDLE session example to show what's happening
>>> class Test():
myList = []
def __init__( self ):
self.myList.append( "a" )
>>> Test.myList
[]
>>> test1 = Test()
>>> Test.myList
['a']
>>> test1.myList
['a']
>>> test2 = Test()
>>> test2.myList
['a', 'a']
>>> test1.myList
['a', 'a']
>>> Test.myList
['a', 'a']
I picked up the book Pro Django which answers this question. It's a great book by the way, and I highly recommend it!
The solution is to make BOTH the choice field and my helper var both instance variables:
class AssignmentFilterForm(forms.Form):
def __init__(self, pg, request = None):
super(forms.Form, self).__init__(request)
self.filters = []
self.filters.append(PatientFilter('All'))
self.filters.append(PatientFilter('Assigned', 'service__isnull', False))
self.filters.append(PatientFilter('Unassigned', 'service__isnull', True))
self.addPhysicians(pg)
self.fields['filter'] = forms.ChoiceField()
for i, f in enumerate(self.filters):
self.fields['filter'].choices.append((i, f.name))
Clearing out the choices works but would surely result in threading issues.
You're appending to the PER-CLASS variable self.filters. Make it into a PER-INSTANCE variable instead, by doing self.filters = [] at the start of __init__.
To clarify from some of the other answers:
The fields are, and must be, class variables. They get all sorts of things done to them by the metaclass, and this is the correct way to define them.
However, your filters variable does not need to be a class var. It can quite easily be an instance var - just remove the definition from the class and put it in __init__. Or, perhaps even better, don't make it a property at all - just a local var within __init__. Then, instead of appending to filters.choices, just reassign it.
def __init__(self, *args, **kwargs):
super(forms.Form, self).__init__(*args, **kwargs)
filters = []
filters.append(PatientFilter('All'))
filters.append(PatientFilter('Assigned', 'service__isnull', False))
filters.append(PatientFilter('Unassigned', 'service__isnull', True))
self.fields["filter"].choices = [(i, f.name) for i, f in enumerate(filters)]
As answered above, you need to initialize filters as an instance variable:
def __init__(...):
self.filters = []
self.filters.append(...)
# ...
If you want to know more about how the Form class works, you should read this page in the Django wiki:
Model Creation and Initialization
It talks about the internals of the Model class, but you'll find the general setup of fields is somewhat similar to the Form (minus the database stuff). It's a bit dated (2006), but I think the basic principles still apply. The metaclass stuff can be a bit confusing if you're new though.