I'm trying to prepare some simple tests for my app. I have a model as below:
class Kategoria(models.Model):
nazwa = models.CharField('Nazwa Kategorii', max_length=30)
class Meta:
verbose_name = "Kategoria"
verbose_name_plural = "Kategorie"
def __str__(self):
return self.nazwa
In the tests.py:
import unittest
from blog.models import Kategoria
class KategoriaTestCase(unittest.TestCase):
def setUp(self):
self.nazwa = 'Nowa_kategoria'
def test_tworzenie_obiektu(self):
tworzenie_nowej_kategoria=Kategoria.objects.create(self.nazwa)
self.assertTrue(tworzenie_nowej_kategoria)
self.assertEqual(nowa_kategoria.nazwa,'Nowa_kategoria')
On the end test fails because:
TypeError: create() takes 1 positional argument but 2 were given
What am I doing wrong?
create takes keywords arguments.
In your test_tworzenie_obiektu method change
tworzenie_nowej_kategoria = Kategoria.objects.create(self.nazwa)
^^^^
to
tworzenie_nowej_kategoria = Kategoria.objects.create(nazwa=self.nazwa)
So you method should be
def test_tworzenie_obiektu(self):
tworzenie_nowej_kategoria = Kategoria.objects.create(nazwa=self.nazwa)
self.assertTrue(tworzenie_nowej_kategoria)
self.assertEqual(nowa_kategoria.nazwa,'Nowa_kategoria')
You need to supply the field name with create(), like this:
def test_tworzenie_obiektu(self):
tworzenie_nowej_kategoria = Kategoria.objects.create(nazwa=self.nazwa)
self.assertTrue(tworzenie_nowej_kategoria)
self.assertEqual(nowa_kategoria.nazwa,'Nowa_kategoria')
You should also confirm the last self.assertEqual, it will always fail since there is no nowa_kategoria variable in your class; you probably want
self.assertEqual(tworzenie_nowej_kategoria.nazwa, self.nazwa)
Note that I removed the hardcoded name, and changed the name of the variable to that of the object being returned.
Related
I've written a custom editable table with columns subclassing from django_tables2.Column, but keep struggling with rendering a select tag in my custom column. Considering the model:
myapp/models.py
from django.db import models
from myapp.utils.enums import MyModelChoices
class MyModel(models.Model):
bound_model = models.ForeignKey(
SomeOtherModel,
related_name='bound_model'
)
used_as = models.CharField(
max_length=50,
blank=True,
null=True,
choices=MyModelChoices.choices()
)
and my enum in myapp/utils/enums.py:
class MyModelChoices:
__metaclass__ = EnumMeta # Logic irrelevant
First = 'First',
Second = 'Second',
Third = 'Third'
I end up with custom column like this:
import django_tables2 as tables
from django.forms import ChoiceField
class ChoicesColumn(tables.Column):
def __init__(self, choices, attrs=None, **extra):
self.choices = choices
kwargs = {'orderable': False, 'attrs': attrs}
kwargs.update(extra)
super(ChoicesColumn, self).__init__(**kwargs)
def render(self, value, bound_column):
select = ChoiceField(choices=self.choices)
return select.widget.render(
bound_column.name,
self.label_to_value(value)
)
def label_to_value(self, label):
for (v, l) in self.choices:
if l == label:
return v
which is later called in my table class like this:
import django_tables2 as tables
from myapp.models import MyModel
from myapp.tables.utils import ChoicesColumn
class MyTable(tables.Table):
name = tables.Column()
used_as = ChoicesColumn(
choices=lambda record: record.used_as.choices()
)
def render_name(self, record):
return record.bound_model.name
class Meta:
model = MyModel
fields = ('name', 'used_as',)
but still there's rendered just a plain <td></td> with text instead of select field. What am I doing wrong in this situation? I'm using Python 2.7, Django 1.8 and django-tables2 1.16.0. Thanks in advance for your advice!
UPDATE
I changed my custom column class like this:
class ChoicesColumn(tables.Column):
def __init__(self, attrs=None, **extra):
kwargs = {'orderable': False, 'attrs': attrs}
kwargs.update(extra)
super(ChoicesColumn, self).__init__(**kwargs)
def render(self, value, bound_column):
options = [self.render_option(c) for c in value]
html_template = '''
<select name={}>{}</select>
'''.format(bound_column.name, options)
return mark_safe(html_template)
def render_option(self, choice):
return '<option value={0}>{0}</option>'.format(choice)
and added a render_options method according to this paragraph in documentation:
class MyTable(tables.Table):
name = tables.Column(accessor='pk')
# With or without accessor it doesn't work neither way
used_as = ChoicesColumn(accessor='used_as')
def render_name(self, record):
return record.bound_model.name
def render_used_as(self, record):
return record.used_as.choices()
class Meta:
model = MyModel,
fields = ('name', 'options',)
but this method isn't even executed on render, what I've spotted while debugging, though the method before it executes when I reload the page and renders data correctly. Is that because name column uses the library class, and options column uses custom class inherited from it? If so, what is my subclass missing?
ANOTHER UPDATE
I figured out what was the previous problem with choices, though it didn't solve the problem :( The thing was that I was passing model instance's field used_as, which was set to None, thus it would never populate the ChoiceField. So, I rolled back my custom column class to the initial variant, and in my table class instead of
used_as = ChoicesColumn(
choices=lambda record: record.used_as.choices()
)
I imported MyModelChoices enum and used it instead of model instance
used_as = ChoicesColumn(choices=MyModelChoices.choices())
and now I see the options passing to constructor, though the render method isn't still called for some mysterious reason =/
LAST UPDATE AS FOR NOW
As for the current moment my custom column and table look like this:
class ChoicesColumn(tables.Column):
def __init__(self, choices, attrs=None, **extra)
self.choices = choices
self.choices.insert(0, ('', '------'))
kwargs = {'orderable': False, 'attrs': attrs}
kwargs.update(extra)
super(ChoicesColumn, self).__init__(**kwargs)
def render(self, value, bound_column):
select = forms.ChoiceField(choices=self.choices)
return select.widget.render(bound_column.name, value)
class MyTable(tables.Table):
name = tables.Column(accessor='pk')
used_as = ChoiceColumn(UsedAs.choices(), accessor='used_as')
def render_name(self, record):
return record.bound_model.name
def render_used_as(self, record):
if record.used_as is None:
return ''
return record.used_as
class Meta:
model = MyModel
fields = ('name', 'used_as')
The ChoiceColumn render method and the corresponding method in table class are never called on rendering stage (unlike the other columns), and I completely give up. Please, be merciful enough either to shoot me or tell me where exactly I'm an idiot :)
So, as I accidentally found out, the problem was in accessor attribute – when changed from
used_as = ChoiceColumn(UsedAs.choices(), accessor='used_as')
to
used_as = ChoiceColumn(UsedAs.choices(), accessor='pk')
it finally rendered. I don't understand why that happened and would be very grateful if someone explained that to me.
There is an easier way:
If you have a Enum column (say used_as), you can change the renderer so that it displays the value (instead of the name). Place this in the Table definition (in class MyTable(tables.Table) ).
def render_used_as(self,value):
v = value.split(".")[1]
members = MyModelChoices.__members__
return (members[v].value)
Note that I was using a bit of a different syntax for the Enum
from enum import Enum
Class MyModelChoices(Enum):
First = 'First'
Second = 'Second'
Third = 'Third'
Note: render_used_as is render_%s with %s = variable name
How does Django translate this <bound method Child.parent_identity of <Child: >> object in a string object, and displays it as such in my django-admin "inline" Child class idparent field ?
What does Django do ?
I have the following application structure:
##========================models.py
...
from django_extensions.db.fields import UUIDField
class Parent(models.Model):
id = UUIDField(primary_key=True)
class Child(models.Model):
parent = models.ForeignKey(Parent)
idparent = models.CharField(max_length=100)
def parent_identity(self):
return self.parent_id
#========================admin.py
class ChildForm(forms.ModelForm):
class Meta:
model = Child
exclude = []
def __init__(self, *args, **kwargs):
super(ChildForm, self).__init__(*args, **kwargs)
#print self.instance.parent_identity
self.initial['idparent'] = self.instance.parent_identity
class ChildInline(admin.TabularInline):
model = Child
extra = 1
form = ChildForm
class ParentAdmin(admin.ModelAdmin):
exclude = []
inlines = [ChildInline]
#list_display, etc
admin.site.register(Parent,ParentAdmin)
My inline idparent field displays the Parent id field CORRECTLY in the admin inline interface. Being a newbie, it's magic for me, because self.instance.parent_identity is initially not a string object.
print self.instance.parent_identity
#it prints : <bound method Child.parent_identity of <Child: >>
But how to explictly print the string content as follows
>>print self.instance.parent_identity
#would print : fffeee29-7ac6-42eb-8a8d-eb212d2365ff
That is, how to get it so as to deal with it in the ChildForm class ?
UPDATE
I do not mind specifically about "UUID in the form when the instance hasn't been created yet"
and i do not want to provide an initial value myself.
I want my still empty (extra) Child fields (one field in my example code: idparent) to contain by default something which is Parent variable.
Is it possible ?
Django templates automatically call any object that is callable; e.g. the callable() function returns True when you pass the object in. From the Variables section in the template documentation:
If the resulting value is callable, it is called with no arguments. The result of the call becomes the template value.
Bound methods are callable, so instead of using self.instance.parent_identity, the template uses the output of self.instance.parent_identity().
In your own code, you generally already know that something is a method and you call it explicitly:
def __init__(self, *args, **kwargs):
super(ChildForm, self).__init__(*args, **kwargs)
self.initial['idparent'] = self.instance.parent_identity()
You can treat the parent_identity method as an attribute; have Python call it automatically without you having to call it explicitly. If you never have to pass in an argument, then that might make sense. You do this by decorating the method with the #property decorator:
class Child(models.Model):
parent = models.ForeignKey(Parent)
idparent = models.CharField(max_length=100)
#property
def parent_identity(self):
return self.parent_id
at which point self.instance.parent_identity will give you the return value of that method.
Take into account that the UUIDField only is given a value on pre-save; it'll be None until the object is saved in a database.
If you really wanted to UUID in the form when the instance hasn't been created yet, you'll have to provide an initial value yourself:
import uuid
class ParentAdmin(admin.ModelAdmin):
exclude = []
inlines = [ChildInline]
def __init__(self, *args, **kwargs):
super(ParentAdmin, self).__init__(*args, **kwargs)
self.fields['id'].initial = uuid.uuid4
You are calling a function, which means you need to use it as such:
self.initial['idparent'] = self.instance.parent_identity()
Alternately you could wrap it with the #property decorator and continue using it as you are, notice that you need to use self.parent.id if you want to access the parent's id:
class Child(models.Model):
parent = models.ForeignKey(Parent)
idparent = models.CharField(max_length=100)
#property
def parent_identity(self):
return self.parent.id
The models defines like the following, Cases has multi Keyword as ForeignKeyword,
from django.db import models
class Keyword(models.Model):
name = models.CharField(max_length=32)
def __unicode__(self):
return self.name
class Case(models.Model):
name = models.CharField(max_length=32)
keywords = models.ForeignKey(Keyword)
def __unicode__(self):
return self.name
If I want to use create get_or_create, it will show that I missed a Keyword_id, like this:
case, flag = Case.objects.get_or_create(name = 'case_name', keywords__name = 'keyword_name')
got a message:
django.db.utils.IntegrityError: dbtest_case.keywords_id may not be NULL
You need to instantiate/get_or_create Keyword first:
keyword, _ = Keyword.objects.get_or_create(name='keyword_name')
case, flag = Case.objects.get_or_create(name='case_name', keywords=keyword)
Hope that works for you.
I have a class as
class PlaylistManager(models.Manager):
def add_playlist(self, name):
playlist = Playlist(name=name)
playlist.save()
return playlist
def get_playlist_with_id(self, id):
return super(PlaylistManager, self).get_query_set().filter(pk=id)
class Playlist(models.Model):
name = models.CharField(max_length=30)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
deleted = models.BooleanField(default=False)
objects = PlaylistManager() # is a customer manager
def __repr__(self):
return '<Playlist name:%s, date_created:%s, date_modified:%s, deleted:%s>' % \
(self.name, self.date_created, self.date_modified, self.deleted)
class Meta:
db_table = 'playlists'
and i test as
def test_get_playlist(self):
playlist = Utility.add_playlist()
self.assertEqual(Playlist.objects.get_playlist_with_id(playlist.id), playlist)
class Utility():
#staticmethod
def add_playlist(playlist_name=PLAYLIST):
return Playlist.objects.add_playlist(playlist_name)
When I run the test, I see error as
AssertionError: [<Playlist name:playlist, date_created:2012-07-18 19:54:12.265909+00:00, date_modified:2012-07-18 19:54:12.265955+00:00, deleted:False>] != <Playlist name:playlist, date_created:2012-07-18 19:54:12.265909+00:00, date_modified:2012-07-18 19:54:12.265955+00:00, deleted:False>
even when the two objects are same.
Is there anything I am missing here?
assertEqual() uses the == operator to compare the classes. The default == operator of user-defined classes compares instances by object identity. This means two instances are only considered equal when they are the same instance.
Compare model id or pk and it will be ok.
The AssertionError is correct. Your get_playlist_with_id returns a Queryset not a Playlist instance. If you want get_playlist_with_id to return the single instance then you should use get not filter.
I have a very simple method:
Class Team(models.Model):
def sides(self):
return SideNames.objects.filter(team=self)
SideNames is another model defined in the same file as Team,
Which when I try and test:
self.assertEquals(len(t.sides()), 2)
I get the following error:
return SideNames.objects.filter(team=self)
AttributeError: 'NoneType' object has no attribute 'objects'
but if I change the test to be
self.assertEquals(len(SideNames.objects.filter(team=t)), 2)
Then I don't get the error. What's the difference between calling SideNames.objects.filter from the test itself and calling the actual method?
For reference, here are the 2 classes in their entirety.
class Team(models.Model):
"""The model for a football team."""
class Admin:
pass
def __unicode__(self):
return u'%s' % self.name
def is_player(self, player):
"""Checks to see if 'player' is a member if this team. Returns True if they are, or False otherwise."""
try:
teamPlayer = TeamPlayers.objects.get(player=player, team=self)
return True
except ObjectDoesNotExist:
return False
def sides(self):
"""Return the side names for this team"""
return SideNames.objects.filter(team=self)
def updateSides(self, side_a, side_b):
"""Update the side names"""
names = SideNames.objects.filter(team=self);
a = SideNames.objects.get(name = names[0].name)
a.name = side_a
a.save()
b = SideNames.objects.get(name = names[1].name)
b.name = side_b
b.save()
name = models.CharField("Team Name", max_length=255)
organiser = models.ForeignKey(User)
class SideNames(models.Model):
"""Holds the names of the sides for each team"""
class Admin:
pass
def __unicode__(self):
"""Pretty print the SideNames object"""
return self.name
team = models.ForeignKey(Team)
name = models.CharField(max_length=128)
By any chance, does your test do something like this:
from myapp import models
...
models.SideNames = None
since that's the only explanation I can think of for why SideNames should be None in the context of that method.
As an aside, the method itself is pointless, as backwards relations are automatically provided by Django, so you could just call t.sidenames_set.all().
In the module that defines the test, you're importing the name SideNames from some other module. In the module where that sides method is defined, the name SideNames is not defined or imported.