Django views calls functions several times - python

I am creating a browser-based RPG where fighting mechanics are built into a model called "Battle". It performs actions on Hero, Monster and Item models according to some formulas. Each action adds a message to a "battle log". A player can issue a fight against another player or NPC in a form. When the form is submitted, it calls the same view, the Battle object is created, the characters are drafted and the game mechanics are run.
For some reason, old "Battle" objects are still "selected" between runs of these views, as long as it's in the same web session. So even though I create a new object, the old battle log gets carried over to this new object.
What am I doing wrong here?
Updated with more context
The fightlog field in the first object is correct.
The fightlog field in the second object is the the first objects data PLUS the new data.
The third fightlog is the first plus second plus third, etc.
views.py
def battle_log(request):
if request.session["last_battle"]:
pk = request.session["last_battle"]
b = Battletest.objects.get(pk=pk)
battle_log = b.fightlog
request.session["last_battle"] = ""
context = { 'battle_log' : battle_log, }
return render(request, 'battle.html', context)
else:
return redirect('/game/monster')
def fight_select_monster(request):
form = SelectCharacter()
if request.method=='POST':
form = SelectCharacter(request.POST)
if form.is_valid():
b = Battletest.objects.create()
b.draft(request.POST.get("character"))
b.start_fight()
b.round()
b.eof()
b.save()
request.session["last_battle"] = b.pk
return redirect('/game/fight/')
context = { 'form': form }
request.session["last_battle"] = ""
return render(request, 'fight.html', context)
models.py
class Battletest(models.Model):
messages = []
fightlog = models.TextField()
opponent = ""
def draft(self, opponent):
CHARACTERS= (
(0, 'Confident Hacker'),
(1, 'Confused Coder'),
)
self.opponent = CHARACTERS[int(opponent)][1]
def start_fight(self):
self.messages.append([0, "You joined the battle."])
self.messages.append([0, self.opponent + "has appeared"])
def round(self):
# have character objects do stuff to eachother until
# some edge case is met.
self.messages.append([1, "You smack " + self.opponent + " in the face"])
self.messages.append([1, self.opponent + " decides to leave this stupid fight"])
def eof(self):
self.messages.append([2, "The fight is over and noone wins"])
self.fightlog = self.messages
forms.py
class SelectCharacter(forms.Form):
CONFIDENTHACKER = 0
CONFUSEDCODER = 1
CHARACTERS= (
(CONFIDENTHACKER, 'Confident Hacker'),
(CONFUSEDCODER, 'Confused Coder'),
)
character = forms.ChoiceField(choices=CHARACTERS)

Ok, your issue is here:
class Battletest(models.Model):
messages = []
opponent = ""
This defines messages and opponent as class attributes
- attributes that belong to the class object itself and as such are shared between all instances of the class, making them, practically, global variables (since class objects are singletons).
What you want here is to make messages an instance attribute by defining int in the initializer (that's what it's for):
class Battletest(models.Model):
fightlog = models.TextField()
def __init__(self, *args, **kwargs):
# let Model do it's own stuff !!!
super(Battletest, self).__init__(*args, **kwargs)
# and add our ones:
self.messages = []
self.opponent = None
As a side note: such mistakes are often the sign someone kind of "jumped in" Django without learning Python's basics first and wrongly assumes that because Django models use class attribute to define db fields, Python's class syntax is the same as Java or PHP where you define attributes at the class top-level. But that's not how Python works and I very strongly suggest that at this point you stop everything and do the whole official Python tutorial - it will saves you a lot of time and pain, really.
As a second side note: in the context of server side web app, you want to avoid any kind of (mutable) global state in your code. Every bit of mutable global state should live in some databaseyour models, sessions, whatever as long as it's external to your code AND can be shared amongst many processes - because in a typical production setup, your app WILL be served by many distinct processes (yes, even if you have one single HTTP front server, it will typically manage a pool of django processes, and requests will arbitrarily dispatched to any of those processes).
Now, you have another issue here:
def eof(self):
# ...
self.fightlog = self.messages
You defined fightlog as a text field, but you're assigning a list of lists to it. What get saved will be a textual representation of the list, which is not very usable.
Theoretically, what you have here is a one to many relationship (a Battletest has many Message), so the proper relational design would be to use a distinct Message model with a ForeignKey on Battletest - as shown in the tutorial (you did the tutorial, did you ?).
Now if you really insist on denormalizing this, the best (less worse at least) solution is to serialize messages to json at save() time and unserialize it back to Python in the initializer. This can be done manually:
import json
class Battletest(models.Model):
fightlog = models.TextField()
def __init__(self, *args, **kwargs):
# let Model do it's own stuff !!!
super(Battletest, self).__init__(*args, **kwargs)
# and add our ones:
if self.fightlog:
self.messages = json.loads(self.fightlog)
else:
self.messages = []
self.opponent = None
# ...
def save(self, *args, **kwargs):
self.fightlog = json.dumps(self.messages)
super(Battletest, self).save(*args, **kwargs)
or using a JSONField (that will more or less automagically take care of this) if your RDBMS support it. Googling for "django JSONField" should provide some hints...
Oh and yes... you have duplicated code here:
class Battletest(models.Model):
# ...
def draft(self, opponent):
CHARACTERS= (
(0, 'Confident Hacker'),
(1, 'Confused Coder'),
)
self.opponent = CHARACTERS[int(opponent)][1]
and here:
class SelectCharacter(forms.Form):
CONFIDENTHACKER = 0
CONFUSEDCODER = 1
CHARACTERS= (
(CONFIDENTHACKER, 'Confident Hacker'),
(CONFUSEDCODER, 'Confused Coder'),
)
character = forms.ChoiceField(choices=CHARACTERS)
You want to factor this out so you have one single point of truth:
class Battletest(models.Model):
CONFIDENTHACKER = 0
CONFUSEDCODER = 1
CHARACTERS= [
(CONFIDENTHACKER, 'Confident Hacker'),
(CONFUSEDCODER, 'Confused Coder'),
]
def draft(self, opponent):
self.opponent = self.CHARACTERS[int(opponent)][1]
and in your forms.py:
from . models import Battletest
class SelectCharacter(forms.Form):
character = forms.ChoiceField(choices=Battltest.CHARACTERS)

Related

OTree / Django send player to results/ exit page if StringField input not in my dict

I’m working with OTree with self: https://otree.readthedocs.io/en/self/
On the first page of my experiment, I ask the players to provide an access code / their matriculation number via StringField. If their input is not in my dict in Constants, I want to send them directly to a page where I tell them „Sorry, you can’t participate“ with the only option for them to click the next button and exit the experiment.
I have tried the following:
in models.py
class Constants(BaseConstants):
name_in_url = 'prisoners_test1'
players_per_group = 2
num_rounds = 1
matriculation_dict = {
'123': ('Adam Smith', 'Economics'),
'124': ('Ada Lovelace', 'Programming'),
'125': ('Charles Babbage', 'Mathematics'),
}
class Player(BasePlayer):
matriculation = models.StringField(label='Please provide your Matriculation Number')
access = models.BooleanField()
def matriculation_not_found(self):
if self.matriculation in Constants.matriculation_dict:
self.access = True
else: self.access = False
in pages.py
class ExcludedPlayer(Page):
def is_displayed(self):
return self.player.access == False
page_sequence = [Matriculation, ExcludedPlayer, P1_Decision, P2_Decision, ResultsWaitPage, Results]
The problem is that the value of access is not updated through my if statement.
My second problem is that even if the page ExcludedPlayer is displayed (b/c I set initial value of access = False), the player is directed to the other pages (P1_Decision, ResultsWaitPage, Results) after clicking next. How can I end the game for the excluded player?
Thank you for your help!
To your first problem:
To update the access field, you need to call your matriculation_not_found method somewhere. A good place for this is the otree built-in method before_next_page in your Matriculation class:
class Matriculation(Page):
def before_next_page(self):
self.player.matriculation_not_found()
Or in newer otree versions (no-self format):
class Matriculation(Page):
#staticmethod
def before_next_page(player, timeout_happened):
player.matriculation_not_found()
To your second problem:
The easiest way to prevent the excluded players from seeing the upcoming pages is to remove the next-button. Simply delete the following line from the ExcludedPlayer.html template:
{{ next_button }}
If for some reason you don't want that, you can also check on each of the upcoming pages in the is_displayed method whether access is allowed or not. For example for the P1_Decision page:
class P1_Decision(Page):
def is_displayed(self):
return self.player.access
And again the same in the new no-self format:
class P1_Decision(Page):
#staticmethod
def is_displayed(player):
return player.access
Another alternative would be to swap out the ExcludedPlayers page to a later app (let's call it 'exculuded_players_app') and skip the pages (and apps) in between using the app_after_this_page method:
class Matriculation(Page):
def before_next_page(self):
self.player.matriculation_not_found()
def app_after_this_page(self, upcoming_apps):
if not self.player.access:
return 'exculuded_players_app'
And again the same in the new no-self format:
class Matriculation(Page):
#staticmethod
def before_next_page(player, timeout_happened):
player.matriculation_not_found()
#staticmethod
def app_after_this_page(player, upcoming_apps):
if not player.access:
return 'exculuded_players_app'

Pushing Arguments into Tables2 using kwargs in Django Python

I am attempting to push data from a DJANGO view into the Tables object, passing it through as an argument. In this case, I would like to pass a variable called doc_id into a Tables2 object called tableName
In this example, I have set doc_id as 1, and pass it into the
View
def editorView(request):
doc_id = 1
table = tableName(UserProfile.objects.filter(), doc_id=doc_id)
Table
class tableName(tables.Table):
tbl_doc_id = None ## Creating a temporary variable
def __init__(self, *args, **kwargs):
temp = kwargs.pop("doc_id") ## Grab doc_ID from kwargs
super(tableName, self).__init__(*args, **kwargs)
self.tbl_doc_id = temp ## Assign to self.tbl_doc_id for use later
### Do something with tbl_doc_id
modelFilter = model.objects.filter(pk = tbl_doc_id)
When running the debugger, I can see that tbl_doc_id is still assigned as None, rather than 1.
What is the correct way to pass arguments into a Tables2 instance? Is it possible?
EDIT: Adding more information for context.
In the real world scenario, I have a view. That view takes an argument from the URL called doc_id. That doc_id is used to grab an object from a model called 'MaterialCollection', and return it as 'mc'.
'mc' is then passed into the table
View
def editorView(request, doc_id):
try:
mc = MaterialCollection.objects.get(pk = doc_id)
except Material.DoesNotExist:
raise Http404("Document does not exist")
config = RequestConfig(request)
unnassigned_User_Table = unassignedUserTable(UserProfile.objects.filter(), mc=mc)
... Other code + Render ...
From my table, I create a custom LinkColumn. That linkColumn is used to construct a URL based upon a number of Attributes from the model 'UserProfile', and from mc.
Table
class unassignedUserTable(tables.Table):
mc = None
def __init__(self, *args, **kwargs):
temp_mc = kwargs.pop("mc")
super(unassignedUserTable, self).__init__(*args, **kwargs)
self.mc = temp_mc
current_Assignment = "NONE"
new_Assignment = "AS"
assign_Reviewer = tables.LinkColumn('change_Review_AssignmentURL' , args=[ A('user'), current_Assignment, new_Assignment, mc, A('id')], empty_values=(), attrs={'class': 'btn btn-success'})
class Meta:
model = UserProfile
... Setup excludes/sequence/attributes...
In this particular instance. mc has a FK to UserProfile (in a 1:M) relationship.
I see that the name of your table class is tableName so if you want __init__ to work as expected please change the line:
super(unassignedUsers, self).__init__(*args, **kwargs)
to
super(tableName, self).__init__(*args, **kwargs)
Beyond this obvious problem, there are some more issues with your code:
Your classes must start with a capital letter (TableName instead of tableName)
Your table classes should end end with -Table (for example NameTable)
I am using django-tables2 for many years and never needed to pass something in __init__ as you are doing here. Are you sure that you really need to do this?
If you want to filter the table's data the filtering must be done to your view - the table will get the filtered data to display.

Why is this class and corresponding attribute not being destroyed between requests?

I can not understand this behavior at all. I asked a question yesterday or the day before thinking it was something with bottle.py, but after trying all kinds of possible solutions, even converting my app over to flask, I have pinpointed the behavior to a single, very simple, class, but I have NO idea why this is happening. It's confusing the hell out of me, but I would really like to understand this if anyone can please shed some light on it.
Ok, so first I have a class called Page which simplifies setting up templates a bit, this is the offending class:
class Page:
"""The page object constructs the webpage and holds associated variables and templates"""
def __init__(self, template=None, name = '', title='',template_vars={}, stylesheets=[], javascript=[]):
# Accepts template with or without .html extension, but must be added for lookup
self.stylesheet_dir = '/css'
self.javascript_dir = '/js'
self.template = template
self.template_vars = {}
self.name = name
self.title = title
self.stylesheets = stylesheets
self.javascript = javascript
self.template_vars['debug'] = _Config.debug
self.template_vars['title'] = self.title
self.template_vars['stylesheets'] = self.stylesheets
self.template_vars['javascript'] = self.javascript
def render(self):
"""Should be called after the page has been constructed to render the template"""
if not self.template.endswith(_Config.template_extension):
self.template += '.' + _Config.template_extension
if not self.title:
if self.name:
self.title = _Config.website + _Config.title_delimiter + self.name
else:
# If title not given use template name
self.title = _Config.website + _Config.title_delimiter + self.template.rstrip('.html')
try:
template = env.get_template(self.template)
except AttributeError:
raise (AttributeError, 'template not set')
rendered_page = template.render(self.template_vars)
return rendered_page
def add_stylesheet(self, name, directory=None):
# Sanitize name
if not name.endswith('.css'):
name += '.css'
if name.startswith('/'):
name = name.lstrip('/')
if not directory:
directory = self.stylesheet_dir
self.template_vars['stylesheets'].append(directory + '/' + name)
def add_javascript(self, name, directory=None):
# Sanitize name
if not name.endswith('.js'):
name += '.js'
if name.startswith('/'):
name = name.lstrip('/')
if not directory:
directory = self.javascript_dir
self.template_vars['javascript'].append(directory + '/' + name)
And here is an example of a route that the problem is exhibited:
#route('/create_account', method=['GET','POST'])
def create_account():
dbsession = db.Session()
page = Page('create account')
page.add_javascript('create_account.js')
if request.method == 'GET':
page.template = 'create_account'
page.template_vars['status'] = None
document = page.render()
dbsession.close()
return document
elif request.method == 'POST':
# Omitted
The problem lies with the method Page.add_javascript(). The next time I go to the /create_account page it is not creating a new Page object and instead reusing the old one. I know this because if I go to the page twice I will have two entires for the create_account.js in my returned html document(the template simply runs a for loop and creates a tag for any js files passed in that list). If I go 3 times it'll be listed 3 times, 40, 40 and so on. Now however if I simply change it to use the initializer and not the add_javascript method the problem goes away.
#route('/create_account', method=['GET','POST'])
def create_account():
dbsession = db.Session()
page = Page('create account', javascript=['create_account.js'])
if request.method == 'GET':
page.template = 'create_account'
page.template_vars['status'] = None
document = page.render()
dbsession.close()
return document
elif request.method == 'POST':
However I suspect something is still wrong and just for my own sanity I need to understand what the hell is going on here. What is possibly happeneing behind the scenes where the add_javascript method would be called twice on the same page object? The method is called immediately after creating a new instance of the page object, where is it possibly getting the old contents of template_vars from?
The problem is that you use mutable defaults for your Page.__init__ function. See http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments.
So you do get a new Page instance on each request, but the lists/dictionaries that hold your javascript etc are re-used.
Replace list/dict default argument values with None, check for None in __init__.

Django receiver check if first create

The idea of the code below should be that it only fires if the field verification_pin is empty i.e. on a new record. However, it seems that every time I save the model it generates a new pin ignoring if instance.verification_pin is None statement, why, what have I missed?
#receiver(pre_save, sender=CompanyUser)
def my_callback(sender, instance, *args, **kwargs):
if instance.verification_pin is None:
instance.verification_pin = instance.generate_pin()
instance.is_active = False
instance.send_verification_pin()
Model:
class CompanyUser(User):
verification_pin = models.IntegerField(max_length=4, null=True)
objects = UserManager()
def generate_pin(self):
"""
Returns a random four digit pin.
"""
return random.randint(999, 9999)
def send_verification_pin(self):
self.email_user(
subject="Test",
message="Your pin: %s" % self.verification_pin,
from_email=settings.DEFAULT_FROM_EMAIL
)
You can set the default value for a field to a callable object.
Or, you might try using a post_save handler instead, where you can check if created is True. Also, it might be helpful to check the value in verification_pin after saving, to see if it really got set or not.

What is the proper way to delineate modules and classes in Python?

I am new to Python, and I'm starting to learn the basics of the code structure. I've got a basic app that I'm working on up on my Github.
For my simple app, I'm create a basic "Evernote-like" service which allows the user to create and edit a list of notes. In the early design, I have a Note object and a Notepad object, which is effectively a list of notes. Presently, I have the following file structure:
Notes.py
|
|------ Notepad (class)
|------ Note (class)
From my current understanding and implementation, this translates into the "Notes" module having a Notepad class and Note class, so when I do an import, I'm saying "from Notes import Notepad / from Notes import Note".
Is this the right approach? I feel, out of Java habit, that I should have a folder for Notes and the two classes as individual files.
My goal here is to understand what the best practice is.
As long as the classes are rather small put them into one file.
You can still move them later, if necessary.
Actually, it is rather common for larger projects to have a rather deep hierarchy but expose a more flat one to the user. So if you move things later but would like still have notes.Note even though the class Note moved deeper, it would be simple to just import note.path.to.module.Note into notes and the user can get it from there. You don't have to do that but you can. So even if you change your mind later but would like to keep the API, no problem.
I've been working in a similar application myself. I can't say this is the best possible approach, but it served me well. The classes are meant to interact with the database (context) when the user makes a request (http request, this is a webapp).
# -*- coding: utf-8 -*-
import json
import datetime
class Note ():
"""A note. This class is part of the data model and is instantiated every
time there access to the database"""
def __init__(self, noteid = 0, note = "", date = datetime.datetime.now(), context = None):
self.id = noteid
self.note = note
self.date = date
self.ctx = context #context holds the db connection and some globals
def get(self):
"""Get the current object from the database. This function needs the
instance to have an id"""
if id == 0:
raise self.ctx.ApplicationError(404, ("No note with id 0 exists"))
cursor = self.ctx.db.conn.cursor()
cursor.execute("select note, date from %s.notes where id=%s" %
(self.ctx.db.DB_NAME, str(self.id)))
data = cursor.fetchone()
if not data:
raise self.ctx.ApplicationError(404, ("No note with id "
+ self.id + " was found"))
self.note = data[0]
self.date = data[1]
return self
def insert(self, user):
"""This function inserts the object to the database. It can be an empty
note. User must be authenticated to add notes (authentication handled
elsewhere)"""
cursor = self.ctx.db.conn.cursor()
query = ("insert into %s.notes (note, owner) values ('%s', '%s')" %
(self.ctx.db.DB_NAME, str(self.note), str(user['id'])))
cursor.execute(query)
return self
def put(self):
"""Modify the current note in the database"""
cursor = self.ctx.db.conn.cursor()
query = ("update %s.notes set note = '%s' where id = %s" %
(self.ctx.db.DB_NAME, str(self.note), str(self.id)))
cursor.execute(query)
return self
def delete(self):
"""Delete the current note, by id"""
if self.id == 0:
raise self.ctx.ApplicationError(404, "No note with id 0 exists")
cursor = self.ctx.db.conn.cursor()
query = ("delete from %s.notes where id = %s" %
(self.ctx.db.DB_NAME, str(self.id)))
cursor.execute(query)
def toJson(self):
"""Returns a json string of the note object's data attributes"""
return json.dumps(self.toDict())
def toDict(self):
"""Returns a dict of the note object's data attributes"""
return {
"id" : self.id,
"note" : self.note,
"date" : self.date.strftime("%Y-%m-%d %H:%M:%S")
}
class NotesCollection():
"""This class handles the notes as a collection"""
collection = []
def get(self, user, context):
"""Populate the collection object and return it"""
cursor = context.db.conn.cursor()
cursor.execute("select id, note, date from %s.notes where owner=%s" %
(context.db.DB_NAME, str(user["id"])))
note = cursor.fetchone()
while note:
self.collection.append(Note(note[0], note[1],note[2]))
note = cursor.fetchone()
return self
def toJson(self):
"""Return a json string of the current collection"""
return json.dumps([note.toDict() for note in self.collection])
I personally use python as a "get it done" language, and don't bother myself with details. This shows in the code above. However one piece of advice: There are no private variables nor methods in python, so don't bother trying to create them. Make your life easier, code fast, get it done
Usage example:
class NotesCollection(BaseHandler):
#tornado.web.authenticated
def get(self):
"""Retrieve all notes from the current user and return a json object"""
allNotes = Note.NotesCollection().get(self.get_current_user(), settings["context"])
json = allNotes.toJson()
self.write(json)
#protected
#tornado.web.authenticated
def post(self):
"""Handles all post requests to /notes"""
requestType = self.get_argument("type", "POST")
ctx = settings["context"]
if requestType == "POST":
Note.Note(note = self.get_argument("note", ""),
context = ctx).insert(self.get_current_user())
elif requestType == "DELETE":
Note.Note(id = self.get_argument("id"), context = ctx).delete()
elif requestType == "PUT":
Note.Note(id = self.get_argument("id"),
note = self.get_argument("note"),
context = ctx).put()
else:
raise ApplicationError(405, "Method not allowed")
By using decorators I'm getting user authentication and error handling out of the main code. This makes it clearer and easier to mantain.

Categories