Implemented basic template functionality. #9
|
@ -18,10 +18,26 @@ from django.urls import re_path,path,include
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.contrib.staticfiles.views import serve
|
from django.contrib.staticfiles.views import serve
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
from postings import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# / routes to index.html
|
# / routes to index.html
|
||||||
url(r'^$', serve, kwargs={'path': 'index.html'}),
|
path('', views.index, name='homepage'),
|
||||||
|
|
||||||
|
# /reviews routes to the endpoint for POSTing new reviews.
|
||||||
|
path('reviews', views.post_review, name='post_review'),
|
||||||
|
|
||||||
|
# /universities routes to a list of universities.
|
||||||
|
path('universities', views.universities, name='universities_list'),
|
||||||
|
|
||||||
|
# /universities/<pk> routes to a specific university.
|
||||||
|
path('universities/<int:university_id>', views.university_entity, name='university entity'),
|
||||||
|
|
||||||
|
# /courses routes to a list of courses.
|
||||||
|
path('courses', views.courses, name='courses_list'),
|
||||||
|
|
||||||
|
# /courses/<pk> routes to a specific course.
|
||||||
|
path('courses/<int:course_id>', views.course_entity, name='course entity'),
|
||||||
|
|
||||||
# static files (*.css, *.js, *.jpg etc.) served on /
|
# static files (*.css, *.js, *.jpg etc.) served on /
|
||||||
# (assuming Django uses /static/ and /media/ for static/media urls)
|
# (assuming Django uses /static/ and /media/ for static/media urls)
|
||||||
|
|
Binary file not shown.
|
@ -10,3 +10,4 @@ admin.site.register(ReviewHelpfulVote)
|
||||||
admin.site.register(University)
|
admin.site.register(University)
|
||||||
admin.site.register(Professor)
|
admin.site.register(Professor)
|
||||||
admin.site.register(Course)
|
admin.site.register(Course)
|
||||||
|
admin.site.register(User)
|
|
@ -0,0 +1,12 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
# The form for creating a review for any sort of rateable entity.
|
||||||
|
class EntityReviewForm(forms.Form):
|
||||||
|
# The integer rating from 1 to 5.
|
||||||
|
rating = forms.IntegerField(min_value=1, max_value=5)
|
||||||
|
# The title of the review.
|
||||||
|
title = forms.CharField(max_length=128)
|
||||||
|
# The textual content of the review.
|
||||||
|
content = forms.CharField(widget=forms.Textarea)
|
||||||
|
# The id of the entity for which the review is created.
|
||||||
|
entity_id = forms.IntegerField()
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.1.1 on 2018-10-02 13:38
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('postings', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='review',
|
||||||
|
name='author',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='postings.User'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 2.1.1 on 2018-10-02 13:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('postings', '0002_auto_20181002_1338'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='reviewhelpfulvote',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='postings.User'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='review',
|
||||||
|
name='author',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='postings.User'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -7,6 +7,10 @@ class User(models.Model):
|
||||||
# The user's birth date.
|
# The user's birth date.
|
||||||
birth_date = models.DateField()
|
birth_date = models.DateField()
|
||||||
|
|
||||||
|
# Returns the name as the string representation of the user.
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
# Represents any object for which reviews can be made. (Universities, Professors, etc.)
|
# Represents any object for which reviews can be made. (Universities, Professors, etc.)
|
||||||
class RateableEntity(models.Model):
|
class RateableEntity(models.Model):
|
||||||
# Constants defined for types of rateable entities.
|
# Constants defined for types of rateable entities.
|
||||||
|
@ -26,6 +30,20 @@ class RateableEntity(models.Model):
|
||||||
# The type of entity this is.
|
# The type of entity this is.
|
||||||
entity_type = models.SmallIntegerField(choices=TYPE_CHOICES)
|
entity_type = models.SmallIntegerField(choices=TYPE_CHOICES)
|
||||||
|
|
||||||
|
# Gets the average of all the reviews.
|
||||||
|
def getAverageRating(self):
|
||||||
|
reviews = self.review_set.select_related()
|
||||||
|
rating_sum = 0
|
||||||
|
for review in reviews:
|
||||||
|
rating_sum += review.rating
|
||||||
|
if reviews.count() == 0:
|
||||||
|
return None
|
||||||
|
return rating_sum / reviews.count()
|
||||||
|
|
||||||
|
# Simply returns the name as the string representation.
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
# A review represents any single data entry to the database.
|
# A review represents any single data entry to the database.
|
||||||
class Review(models.Model):
|
class Review(models.Model):
|
||||||
# An integer rating in the domain [1, 5]
|
# An integer rating in the domain [1, 5]
|
||||||
|
@ -41,7 +59,15 @@ class Review(models.Model):
|
||||||
# The date and time at which the last modification to this review was published.
|
# The date and time at which the last modification to this review was published.
|
||||||
last_updated_date = models.DateTimeField(auto_now=True)
|
last_updated_date = models.DateTimeField(auto_now=True)
|
||||||
# A reference to the person who created this review.
|
# A reference to the person who created this review.
|
||||||
author = models.ForeignKey('postings.User', on_delete=models.PROTECT)
|
author = models.ForeignKey('postings.User', on_delete=models.PROTECT, null=True, blank=True)
|
||||||
|
|
||||||
|
# Gets the total number of votes which marked this review as 'helpful'.
|
||||||
|
def getHelpfulVoteCount(self):
|
||||||
|
ReviewHelpfulVote.objects.filter(pk=self.pk, helpful=True).count()
|
||||||
|
|
||||||
|
# Gets the total number of votes which marked this review as 'unhelpful'.
|
||||||
|
def getUnhelpfulVoteCount(self):
|
||||||
|
ReviewHelpfulVote.objects.filter(pk=self.pk, helpful=False).count()
|
||||||
|
|
||||||
# A vote for a review as either positive or negative.
|
# A vote for a review as either positive or negative.
|
||||||
class ReviewHelpfulVote(models.Model):
|
class ReviewHelpfulVote(models.Model):
|
||||||
|
@ -49,7 +75,8 @@ class ReviewHelpfulVote(models.Model):
|
||||||
review = models.ForeignKey('postings.Review', on_delete=models.CASCADE)
|
review = models.ForeignKey('postings.Review', on_delete=models.CASCADE)
|
||||||
# Whether or not the referenced review was helpful.
|
# Whether or not the referenced review was helpful.
|
||||||
helpful = models.BooleanField()
|
helpful = models.BooleanField()
|
||||||
# TODO: Add a reference to the user who voted. The whole purpose of a separate vote object is to track who votes for what.
|
# The user who made this vote.
|
||||||
|
user = models.ForeignKey('postings.User', on_delete=models.CASCADE)
|
||||||
|
|
||||||
# A RateableEntity for universities.
|
# A RateableEntity for universities.
|
||||||
class University(RateableEntity):
|
class University(RateableEntity):
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "postings/generic_page.html" %}
|
||||||
|
|
||||||
|
{# Represents a generic collection of entities. #}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% block collection_name %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for entity in entities %}
|
||||||
|
<li>
|
||||||
|
{% block entity %}
|
||||||
|
{{ entity.name }}
|
||||||
|
{% endblock %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "postings/collections/collection.html" %}
|
||||||
|
|
||||||
|
{# Represents a list of university entities. #}
|
||||||
|
|
||||||
|
{% block collection_name %}
|
||||||
|
<h2>Courses</h2>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block entity %}
|
||||||
|
<h3><a href="/courses/{{ entity.pk }}">{{ entity.name }}</a></h3>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "postings/collections/collection.html" %}
|
||||||
|
|
||||||
|
{# Represents a list of university entities. #}
|
||||||
|
|
||||||
|
{% block collection_name %}
|
||||||
|
<h2>Universities</h2>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block entity %}
|
||||||
|
<h3><a href="/universities/{{ entity.pk }}">{{ entity.name }}</a></h3>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "postings/entity_pages/entity.html" %}
|
||||||
|
|
||||||
|
{% block entity_info %}
|
||||||
|
Taught at: <a href="/universities/{{ entity.taught_at_university.pk }}">{{ entity.taught_at_university.name }}</a>
|
||||||
|
<h4>Professors</h4>
|
||||||
|
<ul>
|
||||||
|
{% for professor in entity.professors.all %}
|
||||||
|
<li>{{ professor.name }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,50 @@
|
||||||
|
{% extends "postings/generic_page.html" %}
|
||||||
|
|
||||||
|
{# Represents a single entity's detail page. #}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2>Name: {{ entity.name }}</h2> Average rating: {{ entity.average_rating|floatformat:"-2" }}
|
||||||
|
|
||||||
|
{# Child templates can redefine this block for displaying data pertaining to that specific entity. #}
|
||||||
|
{% block entity_info %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{# This section displays all reviews for a given entity. #}
|
||||||
|
<section>
|
||||||
|
<h3>Reviews</h3>
|
||||||
|
<ul>
|
||||||
|
{% for review in entity.review_set.all %}
|
||||||
|
<li>
|
||||||
|
<h4>{{ review.title }}</h4> {{ review.rating }}
|
||||||
|
<p>{{ review.content }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{# This section is where the user can write a review for a particular entity and submit it. #}
|
||||||
|
<section>
|
||||||
|
<h3>Write a Review</h3>
|
||||||
|
<form method="post" action="/reviews">
|
||||||
|
<label for="rating_input">Rating:</label>
|
||||||
|
<input id="rating_input" name="rating" type="number" step="1" min="1" max="5" required>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<label for="title_input">Title:</label>
|
||||||
|
<input id="title_input" name="title" type="text" required>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<label for="content_input">Content:</label>
|
||||||
|
<textarea id="content_input" name="content" required></textarea>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
{# The following csrf_token and input fields are hidden values needed for form submission. #}
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="entity_id" value="{{ entity.pk }}">
|
||||||
|
|
||||||
|
<button type="submit">Submit Review</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends "postings/entity_pages/entity.html" %}
|
||||||
|
|
||||||
|
{% block entity_info %}
|
||||||
|
<h4>Courses</h4>
|
||||||
|
<ul>
|
||||||
|
{% for course in entity.course_set.all %}
|
||||||
|
<li><a href="/courses/{{ course.pk }}">{{ course.name }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<h4>Professors</h4>
|
||||||
|
<ul>
|
||||||
|
{% for professor in entity.professor_set.all %}
|
||||||
|
<li>{{ professor.name }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,23 @@
|
||||||
|
{# This page represents the base template that all others will extend from. #}
|
||||||
|
{# It will contain a universal navigation bar, script tags, footers, and other things needed on every page. #}
|
||||||
|
|
||||||
|
<!doctype HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
{% block title %}RateMyCourse{% endblock %}</title>
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1><a href="/">RateMyCourse</a></h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{# All of a page's content to display should be placed in here. #}
|
||||||
|
<div id="content">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,29 @@
|
||||||
|
{% extends "postings/generic_page.html" %}
|
||||||
|
|
||||||
|
{# The homepage for the website. #}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{# First section for searching our database. #}
|
||||||
|
<section>
|
||||||
|
<form method="GET" action="/">
|
||||||
|
<input type="text" name="search_query">
|
||||||
|
<button type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
<nav>
|
||||||
|
<a href="/universities">Universities</a>
|
||||||
|
<a href="/courses">Courses</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{# Second section for displaying results, or whatever should be shown first. #}
|
||||||
|
{% if results %}
|
||||||
|
<section>
|
||||||
|
<ul>
|
||||||
|
{% for entity in results %}
|
||||||
|
<li>{{ entity.name }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -1,3 +1,75 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.http import HttpResponse, Http404, HttpResponseBadRequest, HttpResponseRedirect
|
||||||
|
from postings.models import *
|
||||||
|
from postings.forms import *
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
|
# The view for the homepage, or index.html
|
||||||
|
# There is an optional 'search_query GET parameter, which, if provided, gives the template a 'results' variable.
|
||||||
|
def index(request):
|
||||||
|
search_query = request.GET.get('search_query', None)
|
||||||
|
results = None
|
||||||
|
if search_query:
|
||||||
|
results = RateableEntity.objects.filter(name__icontains=search_query)
|
||||||
|
|
||||||
|
return render(request, 'postings/index.html', {'results': results})
|
||||||
|
|
||||||
|
# The view for a listing of universities.
|
||||||
|
def universities(request):
|
||||||
|
universities_list = University.objects.all()
|
||||||
|
context = {'entities': universities_list}
|
||||||
|
return render(request, 'postings/collections/universities.html', context)
|
||||||
|
|
||||||
|
# The view for /universities/<pk> Displays one university entity.
|
||||||
|
def university_entity(request, university_id):
|
||||||
|
try:
|
||||||
|
university = University.objects.get(pk=university_id)
|
||||||
|
university.average_rating = university.getAverageRating()
|
||||||
|
except University.DoesNotExist:
|
||||||
|
raise Http404("University does not exist")
|
||||||
|
return render(request, 'postings/entity_pages/university.html', {'entity': university})
|
||||||
|
|
||||||
|
# The view for a listing of courses.
|
||||||
|
def courses(request):
|
||||||
|
courses_list = Course.objects.all()
|
||||||
|
context = {'entities': courses_list}
|
||||||
|
return render(request, 'postings/collections/courses.html', context)
|
||||||
|
|
||||||
|
# The view for a specific course entity.
|
||||||
|
def course_entity(request, course_id):
|
||||||
|
try:
|
||||||
|
course = Course.objects.get(pk=course_id)
|
||||||
|
except Course.DoesNotExist:
|
||||||
|
raise Http404("Course does not exist")
|
||||||
|
return render(request, 'postings/entity_pages/course.html', {'entity': course})
|
||||||
|
|
||||||
|
# The view for receiving POST requests for new reviews.
|
||||||
|
def post_review(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = EntityReviewForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
# Only if the request is a POST and the form is valid do we do anything.
|
||||||
|
rating = form.cleaned_data['rating']
|
||||||
|
title = form.cleaned_data['title']
|
||||||
|
content = form.cleaned_data['content']
|
||||||
|
entity_id = form.cleaned_data['entity_id']
|
||||||
|
entity = RateableEntity.objects.get(pk=entity_id)
|
||||||
|
|
||||||
|
# Creates the new Review object from the posted data.
|
||||||
|
review = Review.objects.create(
|
||||||
|
rating=rating,
|
||||||
|
title=title,
|
||||||
|
content=content,
|
||||||
|
rateable_entity=entity
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send the user back to the entity they were viewing.
|
||||||
|
redirect_path = '/'
|
||||||
|
if entity.entity_type == RateableEntity.UNIVERSITY:
|
||||||
|
redirect_path = '/universities/' + str(entity_id)
|
||||||
|
elif entity.entity_type == RateableEntity.COURSE:
|
||||||
|
redirect_path = '/courses/' + str(entity_id)
|
||||||
|
return HttpResponseRedirect(redirect_path)
|
||||||
|
|
||||||
|
return HttpResponseBadRequest("Bad Request")
|
Loading…
Reference in New Issue