How it can modify formset's forms in an iteration - python

As far as I know if you iterate over a list and update its element, you dont update list itself.
list_var = ['a','b','c']
for l in list_var:
l = 'x'
print list_var
it prints out ['a', 'b', 'c'], not X's
Below code belongs to one of my project.
if hasattr(self.model, 'get_disabled_always_fields'):
for field in self.model.get_disabled_always_fields():
for form in self.formset_instance:
try:
form.fields[field].widget.attrs['readonly'] = True
except KeyError as e:
pass
It updates the list element and it effect the list we were iterating.
Question is , why formset forms affected by for loop modifications.

How variable references work
a = 1 really just means "make a an alias for the value carried by the expression 1". a = 1 + 2 really just means "evaluate 1 + 2 and then make a and alias for the result of that.
However, a[0] = 1 means "set the first value of a to the expression 1".
So, if you're looping through a list, you're essentially going through each value in the iterable and for each value, you set form to that value. Doing form = a within the list just changes the meaning of form. Doing form[0] = a modifies form.
If you don't want it to be modified, consider cloning form at the beginning of the loop using form = form[:].
I hope the concepts of how variables work in this case is now clear; it can be a bit confusing!

In first example you assign elements of list_var to l, then reassigning the same exact l variable.
In second example you operate with form.fields[field].widget.attrs['readonly'] and not field(by which you iterate). So if you would be doing:
for field in self.model.get_disabled_always_fields():
for form in self.formset_instance:
field = 'something' # or
form = 'something2'
nothing would change as it haven't in first example.

Related

To use the parameter as a part of the name for a newly created variable

I wonder if there is a way to create variables automatically using strings, e.g. I have the following code (which does not work properly in Python):
def function(lst1, string1):
lst2 = 'processed_' + string1
lst2 = [] #here I created a string called lst2, but I want to use the string as the variable name.
for i in range(len(lst1)):
if abs(lst1[i]) >= 0.0001 :
lst2.append(i)
return lst2
function(list1, 'price') # list1 is a list which contains the index for column numbers, e.g., [1,2,3]
function(list1, 'promotion')
function(list1, 'calendar')
I would expect that with the function I would be able to create lists such as processed_price, processed_promotion, and processed_calendar, and the function will return these lists.
However the code above would not work as in Python. I wonder how should I write the code properly to achieve the same goal?
getattr(object, name, [default])
setattr(object, name, value)
To get or set values for a variable named via a string, use one of the above as appropriate. However, any time you use user input, it can be a source of injection attacks — the user could use a name that you did not expect them to use but the name is valid so the user gets access to data they should not have access to.
So it is usually advisable to use the user input as a key into a dictionary you define.
dictionary = {
'apple': 'my_value'
}
dictionary[user_input] = 'their_value'

Python: remove() doesn't seems to work

I searched for a while but I can't find a solution to my problem. I'm still new to Python, so I'm sometime struggling with obvious things... Thanks by advance for your advises!
I have a list containing objects and duplicates of these objects, both have specific names: objects_ext and duplicatedObject_SREF_ext. What I want is that if there is a duplicated object in my list, check if the original object is also in list, if it is, remove the duplicated object from list.
I tried to use the remove() method, as there can only be one occurrence of each name in the list, but it doesn't work. Here is my code:
rawSelection = [u'crapacruk_high', u'doubidou_high', u'blahbli_high', u'crapacruk_SREF_high', u'doubidou_SREF_high', u'blahbli_SREF_high']
# objects with '_SREF_' in their names are the duplicated ones
for obj in rawSelection:
if '_SREF_' in str(obj):
rawName = str(obj).split('_')
rootName = rawName [0]
defName = rootName + '_' + '_'.join(rawName[2:])
if defName in rawSelection:
rawSelection.remove (obj)
# Always returns:
# [u'crapacruk_high', u'doubidou_high', u'blahbli_high', u'doubidou_SREF_high']
# Instead of:
# [u'crapacruk_high', u'doubidou_high', u'blahbli_high']
Edit: Oh, forgot to say that the duplicated object must be removed from list only if the original one is in it too.
The problem is that you're mutating the same list you're iterating over.
When you remove u'crapacruk_SREF_high' from the list, everything after it shifts to the left (this done on the C source code level) so the value of obj is now u'doubidou_SREF_high'. Then the end of the for loop comes and obj becomes the next element in the list, u'blahbli_SREF_high'.
To fix this you can copy the list over and get
for obj in rawSelection[:]:
...
You can turn the for loop from for obj in rawSelection: to for obj in list(rawSelection):. This should fix your issue as it iterates over the copy of the list. The way you do it, you modify the list while iterating over it, leading to problems.
rawSelection = [u'crapacruk_high', u'doubidou_high', u'blahbli_high', u'crapacruk_SREF_high', u'doubidou_SREF_high', u'blahbli_SREF_high']
for obj in list(rawSelection):
if '_SREF_' in str(obj):
rawName = str(obj).split('_')
rootName = rawName [0]
defName = rootName + '_' + '_'.join(rawName[2:])
if defName in rawSelection:
rawSelection.remove (obj)
print(rawSelection)
Break the problem up into subtasks
def get_orig_name(name):
if '_SREF_' in name:
return '_'.join(name.split('_SREF_'))
else:
return name
Then just construct a new list with no dups
rawSelection = [u'crapacruk_high',
u'doubidou_high',
u'blahbli_high',
u'crapacruk_SREF_high',
u'doubidou_SREF_high',
u'blahbli_SREF_high']
uniqueList = [ n for n in rawSelection if ('_SREF_' not in n) or
(get_orig_name(n) not in rawSelection ) ]
print uniqueList
You could use filter to get quite a clean solution.
def non_duplicate(s):
return not('_SREF_' in s and s.replace('_SREF', '') in raw_selection)
filtered_selection = filter(non_duplicate, raw_selection)
This will do what you want (note that it doesn't matter what order the items appear in):
rawSelection = list({i.replace('_SREF', '') for i in rawSelection})
This works by iterating through the original list, and removing the '_SREF' substring from each item. Then each edited string object is added to a set comprehension (that's what the {} brackets mean: a new set object is being created). Then the set object is turned back into a list object.
This works because for set objects, you can't have duplicate items, so when an attempt is made to add a duplicate, it fails (silently). Note that the order of the original items is not preserved.
EDIT: as #PeterDeGlopper pointed out in the comments, this does not work for the constraint that the _SREF_ item only gets removed only if the original appears. For that, we'll do the following:
no_SREF_Set = {i for i in rawSelection if '_SREF_' not in i}
rawSelection = list({i.replace('_SREF', '') if i.replace('_SREF', '') in no_SREF_Set else i for i in rawSelection})
You can combine this into a one-liner, but it's a little long for my taste:
rawSelection = list({i.replace('_SREF', '') if i.replace('_SREF', '') in {i for i in rawSelection if '_SREF_' not in i} else i for i in rawSelection})
This works by creating a set of the items that don't have '_SREF_', and then creating a new list (similar to the above) that only replaces the '_SREF' if the no '_SREF_' version of the item appears in the no_SREF_Set.

Django: get object with empty column

I'm looking to do something like this:
u = User.objects.filter(name__isnull=True)[0]
The default value in MySQL for name is None. I also tried:
u = User.objects.filter(name=None)[0]
Neither have returned anything, aka, IndexError: list index out of range. How do I get an object with no value set as the name?
Even thought MySQL says the default value is None, Django used "" as the default, so I used:
User.objects.filter(name="").first()
You are missing some key concepts.
IndexError: list index out of range
This error is not a error looking in the db, this error is given because you are trying to do something with an index of array which does not exist.
a = [1,2,3]
a[0] = 1
a[2] = 2
a[3] = 3
a[4] = IndexError: list index out of range
a[n] = IndexError: list index out of range
you can do normaly:
u = User.objects.filter(name=None)
In the DB if you set Null = True and blank = True (i don't recommend this) in the models you can have 'name=None' and 'name=""'
The problem is you are supposing it must be at least one User with your params, for that you are adding the [0] to retrieve a user instance instead a queryset.
If you expect to retrieve only and only one item of the query you must use the .get, (normally used searching pk, if more than one item is returned it gives an error)
u = User.objects.get(name=None)
but, if you know more than one item can exist in your with the filters, (for instance, the name not the pk) and you only care about the first you use the .first method, and later you check if exist.
User.objects.filter(name="").first()

List as value of session variable in Django

I'm building a small Django app. The user is to build up a list of pairs of strings, by repeatedly submitting a pair of forms. The list is stored as value of a session variable.
But there is a problem. When the list is loaded by the view, the strings which were to be elements of the pairs in the list wind up getting prefixed by the letter u. So for example as elements of the list instead of
['a','b']
['c','d']
we get
[u'a',u'b']
[u'c',u'd']
My impression is that Django treats values of session variables as strings. So maybe the problem has something to do with the conversion of lists?
Here is the source of the view:
def plisting(request):
if 'plist' not in request.session:
request.session['plist']=[]
plist = request.session['plist']
if 'entry' in request.POST:
entry = str(request.POST['entry'])
key = str(request.POST['key'])
plist = plist+[[entry,key]]
request.session['plist'] = plist
return render(request,'evaluator/plisting.html',{'plist':plist})
The u at the beginning of the string returned to your view function just means that the strings are encoded in unicode. You need not handle it specially in your code. Accessing the list just normally will return you the expected strings.
This is what I mean:
>>> a = [u'a',u'b']
>>> b = [u'c',u'd']
>>> a[1]
'b'
>>> b[0]
'c'
As evident from the output, when you access the elements, you do not see the 'u' as part of the strings.
Hope this helps.

Python - efficient way to create 20 variables?

I need to create 20 variables in Python. That variables are all needed, they should initially be empty strings and the empty strings will later be replaced with other strings. I cann not create the variables as needed when they are needed because I also have some if/else statements that need to check whether the variables are still empty or already equal to other strings.
Instead of writing
variable_a = ''
variable_b = ''
....
I thought at something like
list = ['a', 'b']
for item in list:
exec("'variable_'+item+' = '''")
This code does not lead to an error, but still is does not do what I would expect - the variables are not created with the names "variable_1" and so on.
Where is my mistake?
Thanks, Woodpicker
Where is my mistake?
There are possibly three mistakes. The first is that 'variable_' + 'a' obviously isn't equal to 'variable_1'. The second is the quoting in the argument to exec. Do
for x in list:
exec("variable_%s = ''" % x)
to get variable_a etc.
The third mistake is that you're not using a list or dict for this. Just do
variable = dict((x, '') for x in list)
then get the contents of "variable" a with variable['a']. Don't fight the language. Use it.
I have the same question as others (of not using a list or hash), but if you need , you can try this:
for i in xrange(1,20):
locals()['variable_%s' %i] = ''
Im assuming you would just need this in the local scope. Refer to the manual for more information on locals
never used it, but something like this may work:
liste = ['a', 'b']
for item in liste:
locals()[item] = ''

Categories