Newbie django/python with C++ background wants enums

28 views
Skip to first unread message

NENAD CIKIC

unread,
Feb 1, 2012, 4:45:35 PM2/1/12
to django...@googlegroups.com
Hello, the subject expresses my discomfort with certain python characteristics, given my background, and my lack of python knowledge.
Specifically lets say that I have a model with a Text field and char field. The char field is length 1 and says "use or do not use the text field". The char field can have Y or N values.
So using the admin interface I wanted to override the clean method but I did not want to write
if text.__len__==0 and char=='Y':
  raise exception

In C/C++ you would use enum for these sort of things. So I ended with defining in models.py something as:
TO_USE= (
    ('Y', 'Yes'),
    ('N', 'No'),
    )

class X(models.Model):
    txt= models.CharField(db_index=True,null=True, blank=True,max_length=30)
    use_txt= models.CharField(blank=False,max_length=1,default='D',choices=TO_USE)

and in admin.py something as
class XForm(forms.ModelForm):
    def clean(self):
        cleaned_data=super(XForm, self).clean()
        txt= cleaned_data['txt'].strip()
        use_txt=cleaned_data['use_txt'].strip()

        if txt.__len__()==0 and use_txt==TO_USE.__getitem__(0)[0]:
            raise forms.ValidationError('This is needed!')

        return cleaned_data

The part .__getitem__(0)[0] is not very readable. I have looked for enums in python, and if I have understood well, it seems they are not implemented.
What is the best way to do it in python for my problem, given that I do not want to write =='Y'.
Thanks
Nenad

Nikolas Stevenson-Molnar

unread,
Feb 1, 2012, 8:13:58 PM2/1/12
to django...@googlegroups.com
You could use a class, such as:

class TO_USE:
    Y = 'Yes'
    N = 'No'

if char == TO_USE.Y:
    pass


_Nik
--
You received this message because you are subscribed to the Google Groups "Django users" group.
To view this discussion on the web visit https://groups.google.com/d/msg/django-users/-/oqGo6Td_lYoJ.
To post to this group, send email to django...@googlegroups.com.
To unsubscribe from this group, send email to django-users...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

Jeff Heard

unread,
Feb 1, 2012, 8:35:57 PM2/1/12
to django...@googlegroups.com
*slightly* better would be:

class X(models.Model):
    YES='Y'
    NO='N'
    DEFAULT='D'
    TO_USE = ((X.YES, "Yes"), (X.NO, "No"), (X.DEFAULT, "Default"))

    txt= models.CharField(db_index=True,null=True, blank=True,max_length=30)
    use_txt= models.CharField(blank=False,max_length=1,default='D',choices=X.TO_USE)

and in admin.py 

class XForm(forms.ModelForm):
    def clean(self):
        cleaned_data=super(XForm, self).clean()
        txt=cleaned_data['txt'].strip()
        use_txt=cleaned_data['use_txt'].strip()

        if not txt and use_txt==X.YES:

            raise forms.ValidationError('This is needed!')

        return cleaned_data

Chris

unread,
Feb 1, 2012, 8:53:56 PM2/1/12
to Django users
A different way would be to define constants:

YES = 'Y'
NO = 'N'

TO_USE= (
(YES, 'Yes'),
(NO, 'No'),
)

-----------

from myapp.models import YES

class XForm(forms.ModelForm):
def clean(self):
cleaned_data=super(XForm, self).clean()
txt= cleaned_data['txt'].strip()
use_txt=cleaned_data['use_txt'].strip()

if txt.__len__()==0 and use_txt == YES:
raise forms.ValidationError('This is needed!')

return cleaned_data

Mike Dewhirst

unread,
Feb 2, 2012, 12:37:39 AM2/2/12
to django...@googlegroups.com
On 2/02/2012 12:13pm, Nikolas Stevenson-Molnar wrote:
> TO_USE= (
> ('Y', 'Yes'),
> ('N', 'No'),
> )
>
> class X(models.Model):
> txt= models.CharField(db_index=True,null=True,
> blank=True,max_length=30)
> use_txt=
> models.CharField(blank=False,max_length=1,default='D',choices=TO_USE)
>
> and in admin.py something as
> class XForm(forms.ModelForm):
> def clean(self):
> cleaned_data=super(XForm, self).clean()
> txt= cleaned_data['txt'].strip()
> use_txt=cleaned_data['use_txt'].strip()
>
> if txt.__len__()==0 and use_txt==TO_USE.__getitem__(0)[0]:

Personally, I would do this in models.py so it would run in any form's
clean method. In which case ...

class X(models.Model):
...

def clean(self):
self.txt = self.txt.strip()


Assuming you mean you only care about the contents of txt if there is
something there AND then if so, you want the user to make a Yes/No
selection ...

if self.txt:
ok = False
for abbr, fullword in TO_USE:
# fullword is ignored
if abbr in self.use_txt:
ok = True
break
if not ok:
raise django.core.exceptions.ValidationError('Yes or
No required')


The above 'in' keyword means you don't need to use_txt.strip()

> raise forms.ValidationError('This is needed!')
>
> return cleaned_data
>
> The part .__getitem__(0)[0] is not very readable. I have looked for
> enums in python, and if I have understood well, it seems they are not
> implemented.

Have a look at list comprehension in the Python docs. It might help if
you make TO_USE into a list of tuples instead of a tuple of tuples. I'm
not as familiar with list comprehension as I should be and I suspect my
verbose approach above could be squished considerably.

Just as an aside, you ought to be able to extract all the functionality
ordinarily required without having to resort to __internal__() methods.
They are really for people who want to tweak the language in "special"
ways or give their own classes python-like class properties.

> What is the best way to do it in python for my problem, given that I do
> not want to write =='Y'.

I'm not saying the above is the "best" way but it might avoid =='Y'

Mike

bruno desthuilliers

unread,
Feb 2, 2012, 3:51:11 AM2/2/12
to Django users
On Feb 1, 10:45 pm, NENAD CIKIC <nenad.ci...@gmail.com> wrote:
> Hello, the subject expresses my discomfort with certain python
> characteristics, given my background, and my lack of python knowledge.
> Specifically lets say that I have a model with a Text field and char field.
> The char field is length 1 and says "use or do not use the text field".

A BooleanField might be better then.

> The
> char field can have Y or N values.
> So using the admin interface I wanted to override the clean method but I
> did not want to write
> if text.__len__==0 and char=='Y':
>   raise exception

"__magic_methods__" are here to support operators (or operator-like)
implementation, and except for a very few corner cases you should not
call them directly. So here you want "len(text)", not
"text.__len__()".

Also note that the parens are the "call operator", so "text.__len__"
will eval to the "__len__" method object itself (remember that in
Python everythin is an object, including classes, functions and
methods), not to the result of calling this method.

And finally, in Python, empty sequences (at least for the builtin
sequence types), empty dicts, empty strings, numeric zero (int, float
etc) and the None object all have a false value in a boolean context
(FWIW, True and False and the bool type are late additions to the
language). So the pythonic way to test for an empty string is just :

if not txt:
print "txt is empty"

> In C/C++ you would use enum for these sort of things. So I ended with
> defining in models.py something as:
> TO_USE= (
>     ('Y', 'Yes'),
>     ('N', 'No'),
>     )
>
> class X(models.Model):
>     txt= models.CharField(db_index=True,null=True, blank=True,max_length=30)
>     use_txt=
> models.CharField(blank=False,max_length=1,default='D',choices=TO_USE)

I'd still use a BooleanField here as far as I'm concerned, but when it
comes to multiple choices, here's how I do:


class X(models.Model):
USE_YES = 'Y'
USE_NO = 'N'
USE_CHOICES = (
(USE_YES, u"Yes"),
(USE_NO, u"No"),
)

use_txt = models.CharField(
u"Use text",
max_length=1,
choices=USE_CHOICES,
default=""
)


then I can test the value using the class pseudo-constants, ie:


x = X.objects.get(pk=1)
if x.use_txt == x.USE_YES:
pass

> and in admin.py something as
> class XForm(forms.ModelForm):
>     def clean(self):
>         cleaned_data=super(XForm, self).clean()
>         txt= cleaned_data['txt'].strip()
>         use_txt=cleaned_data['use_txt'].strip()
>
>         if txt.__len__()==0 and use_txt==TO_USE.__getitem__(0)[0]:

would be a bit better (or at least a bit less worse <g>) this way:

if not txt and use_txt == TO_USE[0][0]:

But that's still not something I'd be happy with. My above solution
would make it a bit more readable:

if use_txt == X.USE_YES and not txt:


>             raise forms.ValidationError('This is needed!')
>
>         return cleaned_data
>
> The part .__getitem__(0)[0] is not very readable.

Indeed. And not even consistant - if you insist on using
__magic_methods__ instead of the operators, at least go all the way:

TO_USE.__getitem__(0).__getitem__(0)

?-)

Sorry, just kidding <g>


Pavlo Kapyshin

unread,
Feb 2, 2012, 5:32:34 AM2/2/12
to django...@googlegroups.com

NENAD CIKIC

unread,
Feb 2, 2012, 1:05:12 PM2/2/12
to django...@googlegroups.com
thanks, to all.
I have now some code to study and understand.
Nenad

ajohnston

unread,
Feb 2, 2012, 11:11:36 AM2/2/12
to Django users
I would probably do it Bruno's way, since it is more explicit, but
just so you know, there are some enumeration tools in Python. Just not
an 'enum' type:

>>> base_choices = ['No', 'Yes'] <-- transform this any way you want:

>>> choices = list(enumerate(base_choices))
>>> choices
[(0, 'No'), (1, 'Yes')] <-- can be used for 'choices' parameter in
BooleanField

>>> use_txt_choices = {v:k for k,v in enumerate(base_choices)}
>>> use_txt_choices
{'Yes': 1, 'No': 0} <-- can be used in 'clean' method:

if len(txt) == 0 and use_txt==use_txt_choices['Yes']:
pass

If the values have to be boolean instead of ints, you can:
>>> txt_choices = [(True if i else False, j) for i,j in enumerate(base_choices)]
>>> txt_choices
[(False, 'No'), (True, 'Yes')]

>>> lookup_choices = {v:True if k else False for k,v in enumerate(base_choices)}
>>> lookup_choices
{'Yes': True, 'No': False}

ajohnston

unread,
Feb 2, 2012, 2:20:24 PM2/2/12
to Django users
I'm straying a bit off-topic here, but I forgot to mention that other
way I've seen people do 'enum' in Python is:

>>> class Colors(object):
... RED, GREEN, BLUE = range(3)
...
>>> c = Colors()
>>> c.RED
0
>>> c.BLUE
2
>>> c.GREEN
1

Not sure this helps much in this particular case though.
Reply all
Reply to author
Forward
0 new messages