I'm working on a fitness game web app. The idea is that each week has a certain number of tasks that a person must complete before the next week is revealed. Here is my models.py schema for the app so far:
from django.db import models
from django.contrib.auth.models import User
class WeekOne(models.Model):
squats = models.PositiveIntegerField()
lunges = models.PositiveIntegerField()
skipStairs = models.BooleanField()
stairDaysCount = models.PositiveSmallIntegerField()
# Set to true if (squats == 1000), (lunges == 250),
# (skipStairs is True), and (stairDaysCount == 3)
weekOneComplete = models.BooleanField()
class UserProfile(models.Model):
user = models.OneToOneField(User)
weekOne = models.ForeignKey(WeekOne)
I'm lost on a few points. First of all, obviously I want each user to be able to track their own progress and not have any other users be able to see it. Is making weekOne a ForeignKey the best way to do this? If so, how does accessing each user's data work once this relationship is defined? For example, if I wanted to create an addSquats function, would I do something like this:
user = UserProfile()
user.squats += 5
or would I have to do some other magic to get to the squats field?
Second, every time a user makes a change to their progress (i.e., adds a squat or lunge), I want to check if all of the fields have met a certain a benchmark. If they have, I want to set weekOneComplete to true. Each addition will be triggered by some javascript when a user clicks a button -- but where would I put the function that checks/updates the database?
First of all, obviously I want each user to be able to track their own progress and not have any other users be able to see it.
In the template, you can do like this
User: {{ user.username }}
squats: {{ user.userprofile.squats }}
lunges: {{ user.userprofile.lunges }}
Skip Stairs:
{% if user.userprofile.skipStairs %}
Yes
{% else %}
No
{% endif %}
Stair: {{ user.userprofile.stairDaysCount }}}
Week Completed:
{% if user.userprofile.weekOneComplete %}
Yes
{% else %}
In-Progress
{% endif %}
How does accessing each user's data work once this relationship is defined? For example, if I wanted to create an addSquats function.
To access user data,
user = UserProfile(user=request.user)
user.weekOne.squats += 5
user.weekOne.save()
user.save()
Second, every time a user makes a change to their progress (i.e., adds a squat or lunge), I want to check if all of the fields have met a certain a benchmark. If they have, I want to set weekOneComplete to true.
Under your UserProfile model, create check updates function.
class UserProfile(models.Model):
user = models.OneToOneField(User)
weekOne = models.ForeignKey(WeekOne)
def check_updates(self):
check = WeekOne.object.get(id=self.weekOne)
if check.skipStairs and \
check.squats == 1000 and \
check.lunges == 250 and \
check.stairDaysCount == 3:
check.weekOneComplete = True
check.save()
So every time you update the data for that user, just call the method like this:
user = UserProfile.objects.get(user=request.user)
user.check_updates()
To access field of week one you need to do:
user.weekOne.squats += 5
user.weekOne.save()
Actually, it is better to use the F function and do (or similar):
user.weekOne.squats = F('squates') + 5
In general, it is better to do all checkings also in the server side and not to rely on the client (JS or whatever). E.g You expose a url and check for all the POST params that they are integers (e.g. converted to ints)
To your second question: one possible (and simple) way of doing this, since you say you're using javascript, is through an ajax call.
It should point to a url which, itself, points to a view that'll handle the data you're sending in the request. Something along the lines of (say you're using a POST request):
def add(request):
if not request.is_ajax():
raise ...
excercise, amount = request.POST['excercise'], request.POST['amount']
user = request.user
# a model method that'll add to whatever activity
# the user did and update the "week one complete" field
user.did_activity(excercise, amount)
if user.weekOne.weekOneComplete:
return HttpResponse(json.dumps(some_return_data), mimetype="application/json")
return HttpResponse(json.dumps(other_return_data), mimetype="application/json")
This is more to the side of pseudocode, so you get the idea. You'd still need to write the ajax call on the JS side, the model method that'll add to the correct excercise and validate the benchmark, and save the user to the database. For example:
def did_activity(self, excercise, amount):
if excercise == 'squats':
self.weekOne.squats += amount
...
if self.hit_benchmark():
self.weekOne.weekOneComplete = True
self.save()
Also, this example assumes the user is authenticated.
Good luck.
Related
I am just starting to work with Django and I have some problems with forms and dropping lists.
I have a model with two attributes, and I want to display one of the attributes in a dropping down list (this one will be unchangeable) and another one in a text field (this one will be changeable). Also, I have a submit button, so I want to change a second attribute in a text field and by pressing on the button. How can I do this? What would some examples be?
As you are starting to work with Django, you might or might not know about how Django handle forms.
In Django, forms can be handled in two ways:
User-created and managed forms (without any form class)
Class-managed forms (connected to Django models)
Documentation form Django Forms
Now let’s talk about the first type of forms (where you create your HTML form and manage the request sent to server):
These forms are simple to make and when there are only a few and are only suggested when you have a very small amount of inputs (I would say four or fewer inputs).
Here is a simple example of subscription of a newsletter with an email example.
<form id='sub-form' method="POST">
{% csrf_token %}
<div>
<input type="email" name="sub_email">
</div>
<input class="button" value="Subscribe" type="submit" id="subbutton">
</form>
So a very important thing to look at here is {% csrf_token %}, about which you can read more about here and about how it works and prevents cross-site request forgery. This token will be required when you make a request to Django server with any post request and data.
In this subscription form you see one <input> with name="sub_email". Take note of this as we will use this to get this value on the server as this is the key to its value, and then a simple Submit Button.
When you press Submit on a page let’s say url = "http://BASE_URL/home" you will receive a POST request on the view that handles that URL.
So now coming to the view.py, let’s say you only allow registered users to subscribe then the view will go something like this (assuming you are not expecting any other request from the home URL).
def home(request):
user=request.user
if request.method == "POST":
if user.is_authenticated:
email = request.POST['sub_email'] #Using name of input
#Logic to save this email
return HttpResponse("You are Subscribed",status=200)
else:
return HttpReposnse("You are not Authenticated",status=401)
else:
return render(request,"home.html")
Now as you are the expert of simple forms, let’s work with Django class-based forms.
These views are a little work when you have very few inputs, but they are a great help in manageability and when you have to work with large number of inputs.
You will request these Class Based Forms as in your question you are trying to send an instance of a model from your Models.py to a form to user.
I have a model of Posts that can be used for this example:
class Post(models.Model):
postTitle = models.CharField(max_length = 90,null=True)
subTitle = models.CharField(max_length = 160,null=True)
location = models.CharField(max_length = 3,default = 'IN',null=True)
Now according to your question, you are trying to let the user change one attribute, let’s say postTitle and for location you are not letting the user select one of the countries which is preselected and for your post.
Now we have to create a form for this. Forms in class based are created in Forms.py. If you don't have forms.py then you can create one right along models.py and views.py.
Now for the form, I would like to edit some existing data as you are saying one of the attributes (Fields) is fixed and another editable, but you get the value from the model.
class PostEditForm(ModelForm):
location = forms.CharField(label='Country ',widget=forms.Select(attrs={'class': 'Classes_HERE','placeholder':' Select a Country','disabled':'disabled'} ,choices=country_list),required=True)
class Meta:
model = Post
fields= ['postTitle','subTitle','location']
labels = {
'postTitle':'Title',
'subTitle':'Sub-Title',
}
widgets = {
'postTitle': forms.TextInput(attrs={'class': 'mention_class_here','placeholder':' Add Title'}),
'subTitle': forms.TextInput(attrs={'class': 'mention_class_here','placeholder':' Add Sub-Title'})
}
Attributes can be mentioned in forms fields the way I have mentioned them in the above example. I used disabled="disabled" to disable (not editable) location field and used forms.Select to make it drop down.
You might also see that I gave the location field a list to choose from. This is how you can create a list of your items. It's been quite some time when I wrote this, so there might be errors or it may not work for you, so just make sure you are referring to the current documentation and searching Stack Overflow for answers.
country_list = [
('', 'Select a Country'),
("AF", "Afghanistan"),
("AX", "Aland Islands"),
("AL", "Albania"),
("DZ", "Algeria"),
("AS", "American Samoa"),
("AD", "Andorra"),
("AO", "Angola"),
("AI", "Anguilla"),
("AQ", "Antarctica"),
("AG", "Antigua And Barbuda"),
("AR", "Argentina"),
("AM", "Armenia"),
("AW", "Aruba"),
.
.
.
Now this form can be passed as context in a view to an HTML page.
def editPost(request,post_id):
user=request.user
post = get_object_or_404(Post,id=post_id) #Getting the instance of Post
if user.is_authenticated:
formPost = PostEditForm(request.POST or None,instance=post)
if request.method=='POST':
if formPost.is_valid():
savedPost=formPost.save()
else:
return render(request,'postEdit.html',{'formPost':formPost})
else:
return HttpResponse("Not Authorized",status:401)
Now your HTML file postEdit.html should look something like this:
<form id="post-form" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div>
{{formPost}}
</div>
</form>
That is it and adding a submit button in the same form, you can now edit your instance of post that you passed along with this {{formPost}}. Combine your logic wherever you think needs a change to fit in what you want to do.
By no means I am saying all this code is in working condition, but it is shown only to illustrate the flow and working.
I have a django model Request which has a field Approver which is set to the User which has the superuser status. I would like to assign value to this field automatically such that it rotates among the project admins. Any suggestions are welcome. Thanks in advance.
A easy solution that comes to my mind is just in the html template that uses the request model, you can do a function that evaluates a possible asigned user.. something like that:
1. Add a field in your request model that is a foreign key to your user model
Example:
class Request(models.Model):
some other code...
assigned_user=models.ForeignKey(User, on_delete=models.CASCADE)
And add an asigned boolean in your User Model:
class User(models.Model):
assigned=models.BooleanField(default=False)
Then in your html code for creating a request:
You can do something like:
{% for user in Users %}
{% if user.isAdmin %}
{% if not user.assigned %}
<input name="request.varName">{{User.id}}</input>
{% endif %}
{% endif %}
{% endfor %}
Remember that in your view you have to call the request model and your Users model.. You can achieve this in this way:
class RequestCreate(CreateView):
... some other code..
def get_context_data(self, **kwargs):
context = super(RequestCreate, self).get_context_data(**kwargs)
context['Users'] = User.objects.all()
#context['venue_list'] = Venue.objects.all()
#context['festival_list'] = Festival.objects.all()
# And so on for more models
return context
I hope to be usefull here.
You can pass functions as defaults. Define a function like so
def default_func():
last_assigned = Request.objects.latest().Approver # gets latest assigned Approver
# the below if statement always looks for the next superuser pk
if User.objects.filter(is_superuser=True, pk__gt=last_assigned.pk).exists():
next_assigned_superuser = User.objects.filter(is_superuser=True, pk__gt=last_assigned.pk)\
.order_by('pk')[0]
# if there isn't a superuser with a higher pk than the last_assigned_superuser, it will choose
# the superuser below or equal to it with the lowest pk. This will handle the case where there is
# only one superuser without any fuss.
else:
next_assigned_superuser = User.objects.filter(is_superuser=True, pk__lte=last_assigned.pk) \
.order_by('pk')[0]
return next_assigned_superuser
and add this to your Approver field:
# you must pass the function without parenthesis at the end, or else it will
# set the default to whatever the value is at server run time.
# If you pass the function itself, it will be evaluated every time a
# default is needed.
Approver = models.ForeignKey(User, default=default_func)
Edit: Added a return value to default_func. Whoops.
I wrote a function in the Admin form which sets the approver to the User which has the least ammount of requests. Works fine for me.
def get_recipient(self):
approvers = User.objects.annotate(num_of_requests=models.Count('model_field_related_name')).order_by('num_of_requests')
return approvers.first()
So I have a model Listing() that has a field views. In my one of my views, when someone looks at the listing's page, the views field is incremented by one via listing.views = F('views') + 1 and listing.save(update_fields=['views']). My issue is that when I access the views attribute from that same template using {{ listing.views }}, instead of display the current amount of views, the template displays F(views) + Value(1) (literally that text). Now, I assume I could use a Model method such as def get_views() which will return self.views, but I was wondering why I am getting this weird issue. Also, is there a way without writing a model method that I can get the actual integer instead of the odd F(views) + Value(1)?
Here is my current code:
models.py
class Listing(models.Model):
...
views = models.IntegerField(default=0)
listings.py
class ListingView(View):
def get(self, request):
listing_id = request.GET['listing_id']
listing = get_object_or_404(Listing, id=listing_id)
listing.views = F('views') + 1
listing.save(update_fields=['views'])
return render(request, 'listings.html', {'listing': listing})
listings.html
<html>
{{ listing.views }}
</html>
Using F expressions like this requires you to re-fetch the item once saved in order to get updated values (due to the nature of the F expression updating at a database level and not the model instance itself; perhaps that's where the decrease in operational costs come in).
From the docs -
In order to access the new value that has been saved in this way, the object will need to be reloaded:
reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()
I've created a simple 'favourites' system in my django app. I have events which can be faved by any logged in user. What i'm trying to do is somehow check if current logged-in user has faved currently looped event.
It would be quite easy if i could make it in my views.py file but i need to check it directly in template or in a model (in which i can't get currently logged-in user data).
My Event model looks like that:
...
class Event(models.Model):
...
users_faved = models.ManyToManyField(User, related_name='users_faved', blank=True)
If i would be able to access currently logged in user data from Model, i would create a new object called, for example, is_faved() which would be called from a template that way:
{% for event in evemts_list %}
{{ event.is_faved }}
{% endfor %}
But as i've mentioned before, i'm not able to access current logged in user data. Any help would be appreciated! Thanks
Try writing a custom template filter, that takes an event and the user as an argument.
This is using Google App Engine. I am not sure if this is applicable to just normal Django development or if Google App Engine will play a part. If it does, would you let me know so I can update the description of this problem.
class MessageModel(db.Model):
to_user_id = db.IntegerProperty()
to_user = db.StringProperty(multiline=False)
message = db.StringProperty(multiline=False)
date_created = db.DateTimeProperty(auto_now_add=True)
Now when I do a query a get a list of "MessageModel" and send it to the template.html to bind against, I would like to include a few more properties such as the "since_date_created" to output how long ago since the last output, potentially play around with the message property and add other parameters that will help with the layout such as "highlight" , "background-color" etc...
The only way I thought of is to loop through the initial Query Object and create a new list where I would add the property values and then append it back to a list.
for msg in messagesSQL:
msg.lalaland = "test"
msg.since_created_time = 321932
msglist.append(msg)
Then instead of passing the template.html messagesSQL, I will now pass it msglist.
You should still be able to send it messagesSQL to the template after you've added elements to it via the for loop. Python allows that sort of thing.
Something else that might make sense in some cases would be to give your MessageModel methods. For instance, if you have a
def since_date_created(self):
'''Compute the time since creation time based on self.date_created.'''
Then (assuming you have "messagesSQL" in the template), you can use the function as
{% for msg in messagesSQL %}
{{ msg.since_date_created }}
{% endfor %}
Basically, you can call any method in the model as long as you it needs no arguments passed to it.
You can obtain that by defining methods in the model
like
class MessageModel(db.Model):
# Definition
def since_date_created(self):
# ...
Now in the template, you can use it like
Time since created {{ message.since_date_created }}