Commit de59f556 authored by Jean Michel Rouly's avatar Jean Michel Rouly

Merge branch 'dev'

Introduced filters as a feature.
parents 22cd9c20 720984e5
......@@ -6,5 +6,5 @@ emailsettings.py
/researchquestions/static/css/build
/researchquestions/static/admin
/researchquestions/media/feedback.txt
/researchquestions/website/migrations
/researchquestions/settings/secret.py
/researchquestions/researchquestions/secret.py
/researchquestions/config/config.py
## Define your install-specific configurations here.
DICTIONARY_ADJECTIVES = 'dicts/adjectives.en'
DICTIONARY_NOUNS = 'dicts/nouns.en'
BRANDING = ''
PAGE_TITLE_PREFIX = ''
ORGANIZATION = ''
ORGANIZATION_URL = ''
......@@ -3,7 +3,7 @@ import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "researchquestions.settings")
from django.core.management import execute_from_command_line
......
from django.conf import settings
def branding( request ):
return {
'page_title_prefix' : settings.PAGE_TITLE_PREFIX,
'organization' : settings.ORGANIZATION,
'organization_url' : settings.ORGANIZATION_URL,
'branding' : settings.BRANDING,
}
......@@ -22,9 +22,9 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__))
SECRET_KEY = secret.SECRET_KEY
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
DEBUG = True
TEMPLATE_DEBUG = True
TEMPLATE_DEBUG = False
ALLOWED_HOSTS = ['127.0.0.1']
......@@ -42,6 +42,8 @@ INSTALLED_APPS = (
'django.contrib.staticfiles',
'south',
'website',
'website.filters',
'config',
)
MIDDLEWARE_CLASSES = (
......@@ -53,9 +55,9 @@ MIDDLEWARE_CLASSES = (
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'settings.urls'
ROOT_URLCONF = 'researchquestions.urls'
WSGI_APPLICATION = 'settings.wsgi.application'
WSGI_APPLICATION = 'researchquestions.wsgi.application'
MEDIA_URL = '/media/'
MEDIA_ROOT = (os.path.join(BASE_DIR, 'media/'))
......@@ -74,7 +76,6 @@ STATICFILES_FINDERS = (
TEMPLATE_DIRS = (
(os.path.join(BASE_DIR, 'templates/')),
#'/www/http/research-questions/researchquestions/templates/',
'templates',
)
......@@ -83,14 +84,18 @@ TEMPLATE_LOADERS = (
'django.template.loaders.app_directories.Loader',
)
TEMPLATE_CONTEXT_PROCSSORS = (
'django.core.context_processors.request',
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
)
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'django.core.context_processors.request',
LOGIN_URL = '/login'
LOGOUT_URL = '/logout'
LOGIN_REDIRECT_URL = '/'
'researchquestions.context_processors.branding',
)
# Database
# https://docs.djangoproject.com/en/1.6/ref/settings/#databases
......@@ -122,11 +127,12 @@ USE_TZ = True
# Authentication
# http://pythonhosted.org/django-auth-ldap
LOGIN_URL = '/login'
LOGOUT_URL = '/logout'
LOGIN_REDIRECT_URL = '/'
import ldap
# Baseline configuration
# Keep ModelBackend around for per-user permissions and maybe a local
# superuser.
AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
......@@ -145,39 +151,21 @@ AUTH_LDAP_GLOBAL_OPTIONS = { # ignore UAC cert.
ldap.OPT_X_TLS_REQUIRE_CERT : ldap.OPT_X_TLS_NEVER,
}
# Populate the Django user from the LDAP directory.
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail"
}
#AUTH_LDAP_USER_FLAGS_BY_GROUP = {
# "is_active": "cn=active,ou=django,ou=groups,dc=example,dc=com",
# "is_staff": "cn=staff,ou=django,ou=groups,dc=example,dc=com",
# "is_superuser": "cn=superuser,ou=django,ou=groups,dc=example,dc=com"
#}
#AUTH_LDAP_PROFILE_FLAGS_BY_GROUP = {
# "is_awesome": "cn=awesome,ou=django,ou=groups,dc=example,dc=com",
#}
# This is the default, but I like to be explicit.
AUTH_LDAP_ALWAYS_UPDATE_USER = True
# Use LDAP group membership to calculate group permissions.
#AUTH_LDAP_FIND_GROUP_PERMS = True
AUTH_LDAP_ALWAYS_UPDATE_USER = True
# Cache group memberships for an hour to minimize LDAP traffic
#AUTH_LDAP_CACHE_GROUPS = True
#AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
# Install-specific configurations.
from config import config
# Auth Logging (off by default)
#import logging, logging.handlers
#logfile = "/tmp/django-ldap-debug.log"
#my_logger = logging.getLogger('django_auth_ldap')
#my_logger.setLevel(logging.DEBUG)
#
#handler = logging.handlers.RotatingFileHandler(
#logfile, maxBytes=1024 * 500, backupCount=5)
#
#my_logger.addHandler(handler)
DICTIONARY_ADJECTIVES = (os.path.join(STATIC_ROOT, config.DICTIONARY_ADJECTIVES))
DICTIONARY_NOUNS = (os.path.join(STATIC_ROOT, config.DICTIONARY_NOUNS))
PAGE_TITLE_PREFIX = config.PAGE_TITLE_PREFIX
ORGANIZATION = config.ORGANIZATION
ORGANIZATION_URL = config.ORGANIZATION_URL
BRANDING = config.BRANDING
......@@ -7,19 +7,13 @@ admin.autodiscover()
handler404 = 'website.views.error_404'
handler500 = 'website.views.error_500'
urlpatterns = patterns('website.views',
#### STATIC PAGES ####
url(r'^help$', 'help', name='help'),
#### DYNAMIC PAGES ####
url(r'^submit$', 'submit_question', name='submit_question'),
url(r'^feedback$', 'feedback', name='feedback'),
url(r'^$', 'index', name='homepage'),
url(r'^question/(?P<slug>[^\.]+)$', 'view_question', name='view_question'),
url(r'^me$', 'my_questions', name='my_questions'),
urlpatterns = patterns('',
#### ADMIN PAGES ####
#### Dynamic website pages
url(r'^', include('website.urls')),
url(r'^filter/', include('website.filters.urls')),
#### Admin pages
url(r'^admin/', include(admin.site.urls)),
)
......
......@@ -8,7 +8,7 @@ https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "researchquestions.settings")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
from django.contrib import admin
# Register your models here.
from django.db import models
# Create your models here.
from django.test import TestCase
# Create your tests here.
from django.conf.urls import patterns, include, url
urlpatterns = patterns('website.views',
#### Available filters
url(r'^section/(?P<section>[a-zA-Z]+ {0,1}[0-9]*)$', 'index',
name='filter'),
url(r'^(?P<sort>[a-z]*)$', 'index', name='sort'),
url(r'^section/(?P<section>[a-zA-Z]+ {0,1}[0-9]*)/(?P<sort>[a-z]*)$',
'index', name='filter'),
)
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
@login_required
def base_redirect(request):
return redirect('website.views.index')
@login_required
def filter_section(request, section):
return render(request, 'index.html', {
},
)
@login_required
def filter_date(request):
return render(request, 'index.html', {
},
)
@login_required
def filter_comments(request):
return render(request, 'index.html', {
},
)
......@@ -2,18 +2,38 @@ from website.models import Question, Comment, Reply
from django import forms
from django.db import models
from django.forms import ModelForm, Textarea
from django.forms import ModelForm, Textarea, TextInput
class QuestionForm( ModelForm ):
class Meta:
model = Question
fields = ('text',)
fields = ('text','section',)
exclude = ('user','date','rating')
localized_fields = ('date',)
labels = {
'text' : 'Question Text',
'section' : 'Course Section',
}
widgets = {
'text':Textarea(attrs={'class': 'form-control',}),
'text':Textarea(attrs={
'class': 'form-control',
'placeholder': 'Question Text',
}),
'section': TextInput(attrs={
'class': 'form-control',
'placeholder': 'Course Section',
'pattern': '[a-zA-Z]+ {0,1}[0-9]*',
}),
}
# Parse out unneeded spaces from the section. This makes everything
# nice and sanitary and uniform.
def clean_section(self):
data = self.cleaned_data.get('section')
if data is not None:
data = data.upper().replace(" ", "")
return data
class CommentForm( ModelForm ):
class Meta:
model = Comment
......@@ -43,3 +63,11 @@ class ReplyForm( ModelForm ):
class FeedbackForm( forms.Form ):
text = forms.CharField(
widget=forms.Textarea(attrs={'class':'form-control'}),max_length=1000)
class CourseSectionFilterForm( forms.Form ):
section = forms.CharField(
widget = forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Course Section',
'pattern': '[a-zA-Z]+ {0,1}[0-9]*',
}), max_length = 10)
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Question'
db.create_table(u'website_question', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
('date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2014, 3, 7, 0, 0))),
('text', self.gf('django.db.models.fields.TextField')(max_length=1000)),
('rating', self.gf('django.db.models.fields.IntegerField')(default=0)),
('section', self.gf('django.db.models.fields.TextField')(max_length=10)),
))
db.send_create_signal(u'website', ['Question'])
# Adding model 'Comment'
db.create_table(u'website_comment', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
('date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2014, 3, 7, 0, 0))),
('text', self.gf('django.db.models.fields.TextField')()),
('parent', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['website.Question'])),
))
db.send_create_signal(u'website', ['Comment'])
# Adding model 'Reply'
db.create_table(u'website_reply', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
('date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2014, 3, 7, 0, 0))),
('text', self.gf('django.db.models.fields.TextField')()),
('parent', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['website.Comment'])),
))
db.send_create_signal(u'website', ['Reply'])
def backwards(self, orm):
# Deleting model 'Question'
db.delete_table(u'website_question')
# Deleting model 'Comment'
db.delete_table(u'website_comment')
# Deleting model 'Reply'
db.delete_table(u'website_reply')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'website.comment': {
'Meta': {'object_name': 'Comment'},
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 3, 7, 0, 0)'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['website.Question']"}),
'text': ('django.db.models.fields.TextField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
},
u'website.question': {
'Meta': {'ordering': "['-rating']", 'object_name': 'Question'},
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 3, 7, 0, 0)'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'rating': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'section': ('django.db.models.fields.TextField', [], {'max_length': '10'}),
'text': ('django.db.models.fields.TextField', [], {'max_length': '1000'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
},
u'website.reply': {
'Meta': {'object_name': 'Reply'},
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 3, 7, 0, 0)'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['website.Comment']"}),
'text': ('django.db.models.fields.TextField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
}
}
complete_apps = ['website']
\ No newline at end of file
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Question.section'
db.alter_column(u'website_question', 'section', self.gf('django.db.models.fields.CharField')(max_length=10))
def backwards(self, orm):
# Changing field 'Question.section'
db.alter_column(u'website_question', 'section', self.gf('django.db.models.fields.TextField')(max_length=10))
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'website.comment': {
'Meta': {'object_name': 'Comment'},
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 3, 8, 0, 0)'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['website.Question']"}),
'text': ('django.db.models.fields.TextField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
},
u'website.question': {
'Meta': {'ordering': "['-rating']", 'object_name': 'Question'},
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 3, 8, 0, 0)'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'rating': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'section': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}),
'text': ('django.db.models.fields.TextField', [], {'max_length': '1000'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
},
u'website.reply': {
'Meta': {'object_name': 'Reply'},
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 3, 8, 0, 0)'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['website.Comment']"}),
'text': ('django.db.models.fields.TextField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
}
}
complete_apps = ['website']
\ No newline at end of file
......@@ -2,7 +2,8 @@ from django.db import models
from django.utils import timezone
from datetime import datetime
from django.contrib.auth.models import User
from settings import settings
from django.conf import settings
from django.core.validators import RegexValidator
import hashlib
import os
......@@ -12,6 +13,12 @@ class Question( models.Model ):
date = models.DateTimeField(default=timezone.now())
text = models.TextField(max_length=1000)
rating = models.IntegerField(default=0)
section_regex = RegexValidator(regex = r'[a-zA-Z]+ {0,1}[0-9]*')
section = models.CharField(
max_length=10,
blank=True,
validators = [section_regex]
)
class Meta:
ordering = ["-rating"]
......@@ -34,7 +41,7 @@ class Comment( models.Model ):
user = models.ForeignKey(User)
date = models.DateTimeField(default=timezone.now())
text = models.TextField()
parent = models.ForeignKey('Question')
parent = models.ForeignKey('Question', related_name='comments', related_query_name='comments')
def get_replies(self):
replies = Reply.objects.filter(parent__pk=self.pk)
......@@ -73,14 +80,14 @@ def anonymized( obj ):
adj_hash = int(str(n)[4:6])*8 # 2 digits *8 allows for 800 users
adj = "Anonymous"
adjs = open(os.path.join(settings.MEDIA_ROOT, 'adjectives'), 'r')
adjs = open(settings.DICTIONARY_ADJECTIVES, 'r')
for i, line in enumerate(adjs):
if i == adj_hash:
adj = line.title()
break
noun = "Student"
nouns = open(os.path.join(settings.MEDIA_ROOT, 'nouns'), 'r')
nouns = open(settings.DICTIONARY_NOUNS, 'r')
for i, line in enumerate(nouns):
if i == noun_hash:
noun = line.title()
......
{% extends 'layouts/base.html' %}
{% block title %}
HNRS 110 &bull; Error: Page Not Found
Error: Page Not Found
{% endblock %}
{% block content %}
......
{% extends 'layouts/base.html' %}
{% block title %}
HNRS 110 &bull; Error: Page Not Found
Error: Page Not Found
{% endblock %}