I have a QTreeView GUI based on editabletreemode.py in the PyQt examples. Inside the model, I re-implemented setData() for my need: for some out-of-bound value, I'm returning False, otherwise, it returns True
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role != QtCore.Qt.EditRole:
return False
item = self.getItem(index)
result = item.setData(index.column(), value)
if result:
self.dataChanged.emit(index, index)
self.modified = True
print "setData() returning ", result
return result
Problem is, even when setData is returning False, GUI still accepts the changed value. So I now have inconsistent data between the model and view. What would make sense to me is that when setData() return False to reject the value, view should revert back to old value. Is this possible?
[SOLVED]
Actually the return value of setData() doesn't seem to matter. The QTreeView seem to call data() to re-retrieve the value afterwards anyway. The problem I was having was because setData() changed the internal data even though it returned False.
If somebody could explain to me what is the return value of setData() is used for, that'd be great.
The only issue I can see is the signature of .setData(), which is .setData(index, value, role). Neither your code snippet nor the description of your problem are verbose enough to say anything else.
Edit: Indeed, after looking at the Qt sources, I stand corrected. Unlike I've stated before this edit, the return value of .setData() is not actually used by the view.
The data is committed to the model by .setModelData() of the delegate. Normally, Qt uses a QStyledItemDelegate, whose .setModelData() method actually ignores the return value of .setData(). Thus the view does indeed not care for whether the data was successfully set. When the editor for a cell in the view is closed, the view just displays whatever is now the value of that cell (as retrieved by .data()).
However, the return value of .setData() is still important, and well-behaving models should take care to return a proper value. Models generally abstract data sources, and are in and by themselves independent from views. Thus models are also accessed directly, and in this case, the caller needs to check the return value of .setData() to know, whether the operation was successful.
Related
Codenerix
Has anyone knows how to use correctly ng-readonly in a GenModelForm when coming from a sublist tab (GenList) who calls a GenCreateModal windows?
Structure is a master-detail, sublist tab has pk of master table and calls GenCreateModal with this pk argument of master table.
GenCreateModal receives pk argument in his asociated form (the mentioned GenModelForm) and can use it. The goal is to disable field with ng-disabled if pk argument of master table is filled. This way when create from another list of detail table without arguments, field can be filled with a value selecting it with the dropdown, and when coming from master table it cannot be modified and it will be assigned to master pk value.
I tried to do it that way:
First assign 'client' on GenCreateModal with:
def get_initial(self):
client = self.kwargs.get('pk', None)
if client:
self.kwargs['client'] = client
return self.kwargs
Then read it on the GenModelform with:
def __init__(self, *args, **kwargs):
super(DetailForm, self).__init__(*args, **kwargs)
if kwargs.get('initial', None) and kwargs['initial'].get('client', None):
self.fields['client'].widget.attrs[u'ng-readonly'] = 'true'
But it do not work with dropdown fields. Field can be modified.
Cause is that in templatetags_list.py of codenerix we have:
def inireadonly(attrs, i):
field = ngmodel(i)
return addattr(attrs, 'ng-readonly=readonly_{0}'.format(field))
This code set ng-readonly to "true readonly_client" instead of "true" when it comes with value "true" from GenModelForm, values are concatenated.
I found a workaround with:
self.fields['client'].widget.attrs[u'ng-readonly'] = 'true || '
this way the end value will be "true || readonly_client" that result on "true" as desired when evaluated, but I think it is not the proper way.
On my personal fork of django-codenerix I have changed the function to (functions is on two py files, should change both):
def inireadonly(attrs, i):
field = ngmodel(i)
if attrs.get('ng-readonly', None) is None:
attrs = addattr(attrs, 'ng-readonly=readonly_{0}'.format(field))
return attrs
This way it respects the value when it comes filled form GenModelForm, but I'm not sure about inconveniences and collateral effects. For example when want to concatenate conditions, with that change should read old value, concatenate manually and set new value. I think it should be a better way to do it and 'ng-readonly=readonly_{0}'.format(field) should have a functionality that I haven't discovered yet. Don't want to lose it when I discover it. So I revert the change and look for another solution.
Currently I'm using
self.fields['client'].widget.attrs[u'ng-disabled'] = 'true'
and it goes OK, I'm using this way and I have no problem now, but I'm curious about the way to use ng-readonly if I need it on the future. That's because with ng-readonly we can select text on fields with the mouse for example and can not select it with ng-disabled. In some cases it could be of interest.
Has anyone knows how to use ng-readonly in a correct way?
Has anyone knows the functionality of 'ng-readonly=readonly_{0}'.format(field)?
You can define an extra attribute to your fields in your forms. Add {'extra': ['ng-disabled=true']} in the field of your GenModelForm, inside your __groups__ method. Example:
def __groups__(self):
g = [
(_('Info'), 12,
['client', 6, {'extra': ['ng-disabled=true']}],
)
]
return g
You should use ng-disabled as you are doing. This is the way we do it in Django Codenerix Example # Gibhub (lines 41 and 42) and this is how it has been developed for StaticSelects (line 228) as well.
i would like to know how onchange function works with boolean and integer fields.
Suppose if one boolean field get changed to True, the value of respective integer should be changed.
Thanks in advance.
#api.onchange
This decorator will trigger the call to the decorated function if any of the fields specified in the decorator is changed in the form:
#api.onchange('fieldx')
def do_stuff(self):
if self.fieldx == x:
self.fieldy = 'toto'
In previous sample self corresponds to the record currently edited on the form. When in on_change context all work is done in the cache. So you can alter RecordSet inside your function without being worried about altering database. That’s the main difference with #api.depends
At function return, differences between the cache and the RecordSet will be returned to the form.
View management
One of the great improvement of the new API is that the onchange are automatically inserted into the form for you in a simple way. You do not have to worry about modifying views anymore.
Warning and Domain
To change domain or send a warning just return the usual dictionary. Be careful not to use #api.one in that case as it will mangle the dictionary (put it in a list, which is not supported by the web client).
Can someone help me understand the update_field argument for Django signals?
According to the docs:
update_fields: The set of fields to update explicitly specified in the
save() method. None if this argument was not used in the save() call.
I'm not clear on what this means. I was trying to use it to prevent a signal function from executing unless certain fields were updated:
#receiver(post_save, sender=SalesRecord)
def spawn_SaleSource_record(sender, update_fields, created, instance, **kwargs):
if created or update_fields is 'sale_item' or 'sales_qty':
*do function*
However, it seems that it still executes during another signal process when an object is saved, even if an unspecified field is explicitly updated:
x = SalesRecord.objects.filter(paid_off=False, customer=instance.customer).first()
x.paid_off = True
x.save(update_fields=['paid_off'])
Am I going about this wrong?
Your condition does not correspond to what you want as 'sales_qty' is always true.
You want your condition to be:
if created or 'sale_item' in update_fields or 'sales_qty' in update_fields:
What I am trying isn't difficult, however it really isn't working. I cannot see where an error is. In my abstract user model I have an is_donator method that just does not work.
#property
def is_donator(self):
if(self.donator >= datetime.now()):
return True
else:
return False
Some reason it just does not return anything, it all looks alright to me though, any ideas?
You have two related issues. The first is you are using the wrong comparison.
if(self.donator >= datetime.now()):
This means that the donor must become a donor at some point in the future.
Change it to
if(self.donator <= datetime.now()):
This will ensure they became a donor in the past.
The other issue you have is that you are using auto_now:
Automatically set the field to now every time the object is saved.
Useful for “last-modified” timestamps. Note that the current date is
always used; it’s not just a default value that you can override.
This, then, relates to your first issue. Every time the user field is updated - if you don't explicitly set your field - it defaults to now.
Update based on comments: This is checking if your donator is not null and also ensure that it exists. If it doesn't exist, it is up to you to determine your logic, though if it doesn't exist, your user isn't a donator. You can probably just return False in your except block. This block is pretty verbose, but it shows you explicitly what is happening.
def is_donator(self):
try:
if self.donator: # This is checking that self.donator is not null
if(self.donator >= datetime.now()):
return True
else:
return False
else:
# return false? # This runs if self.donator is null
except NameError:
# do something (or just 'pass', because your self.donator value doesn't exist)
Newcomer to Django, so forgive me if I'm missing something obvious or doing something stupid here...
I have a Django model with a custom save() extension. Trimming the unrelated fields and methods, it looks like this:
class Content(models.Model):
url = models.URLField(max_length=1000)
image = models.URLField(blank=True, max_length=1000)
image_type = models.NullBooleanField(default=None)
def save(self, *args, **kwargs):
if self.url:
img, img_type = image_extractor(self.url)
print 'extractor returned: ', img_type
print 'before set: ', self.image_type
setattr(self, 'image', img)
setattr(self, 'image_type', img_type)
print 'after set: ', self.image_type
super(Content, self).save(*args, **kwargs)
print 'from query: ', Content.objects.get(url=self.url).image_type
The image_extractor function returns a url string and a boolean value representing the image type: True for images larger than a certain width, False for smaller images, or None if no suitable image was found. The problem is, when I query Content.objects and check image_type, all objects return either True or None. Hence the print statements. The images with image_type None or True work as expected, but when image_type is returned False, this is what prints:
extractor returned: False
before set: None
after set: False
from query: True
Somehow the call to super().save always reverts image_type to True if I try to set False (but not if I set None). I admit, I don't understand super() very well, but based on the documentation it seems to be necessary when extending the model function. Can anyone explain what's going on here, and how I can fix it?
Edit: I notice that I can change the value to False through the admin page and it stays False. But I still need to fix the save() method. For now, I have just replaced the boolean field with an integer field and saved values as 0 or 1, which does work. But I'd still like to know why the more appropriate field doesn't.
Since writing this question, I had moved ahead with a temporary solution of using an IntegerField to store the boolean as 0 or 1. Upon deciding to revisit the problem tonight and switching back to the NullBooleanField, suddenly everything works as intended. So it seems danodonovan was correct: the code above is correct, and unfortunately I still have no idea what was causing the error. Something else I changed while using the IntegerField must have resolved the issue. Thanks to those of you who have taken a look and offered some opinions, and I'm sorry to have wasted your time.
the way you overload save method looks good.
I think you are not loading the good object from database.
Use the primary key will probably works better.
In your case just replace that:
Content.objects.get(url=self.url).image_type
by that:
Content.objects.get(pk=self.pk).image_type
Looks at the documentation: https://docs.djangoproject.com/en/dev/ref/models/instances/#the-pk-property
Extract from the django documention:
Once the object has been saved, you must reload the object in order to
access the actual value that was applied to the updated field:
product = Products.objects.get(pk=product.pk)
print(product.number_sold)