Commit 645a9048 authored by Daniel W Bond's avatar Daniel W Bond
Browse files

resolved merge conflicts from things that have since happened on amherst

parents ba71b503 fea94a9c
......@@ -11,6 +11,7 @@ develop-eggs/
dist/
downloads/
eggs/
.idea
lib/
lib64/
parts/
......
......@@ -18,10 +18,12 @@ Please visit the [SRCT Wiki](http://wiki.srct.gmu.edu/) for more information on
These instructions are for Ubuntu and Debian, or related Linux distributions. (If you'd like to help write the instructions for Mac OSX, please do!)
### Prerequisities
### Prerequisities and Package Installation
First, install python, pip, and git on your system. Python is the programming language used for Django, the web framework used by Roomlist. 'Pip' is the python package manager. Git is the version control system used for SRCT projects.
**Debian/Ubuntu**
Open a terminal and run the following commands.
`sudo apt-get update`
......@@ -32,23 +34,42 @@ This retrieves links to the most up-to-date and secure versions of your packages
you install python and git.
Now, we're going to clone down a copy of the Roomlist codebase from git.gmu.edu, the SRCT code respository.
Next, install these packages from the standard repositories
Configure your ssh keys by following the directions at git.gmu.edu/help/ssh/README.
`$ sudo apt-get install libldap2-dev mysql-server mysql-client libmysqlclient-dev python-mysqldb libsasl2-dev libjpeg-dev redis-server`
Now, on your computer, navigate to the directory in which you want to download the project (perhaps one called development/ or something similar), and run
If prompted to install additional required packages, install those as well.
`git clone git@git.gmu.edu:srct/roomlist.git`
When prompted to set your mysql password, it's advisable to set it as the same as your normal superuser password.
### Package Installation
Now you're ready to set up the Roomlist repository on your machine.
Next, install these packages from the standard repositories
**macOS (Formerly OS X)**
`$ sudo apt-get install libldap2-dev mysql-server mysql-client libmysqlclient-dev python-mysqldb libsasl2-dev libjpeg-dev redis-server`
This tutorial uses the third party Homebrew package manager for macOS, which allows you to install
packages from your terminal just as easily as you could on a Linux based system. You could use another
package manager (or not use one at all), but Homebrew is highly reccomended.
If prompted to install additional required packages, install those as well.
To get homebrew, run the following command in a terminal:
``/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
When prompted to set your mysql password, it's advisable to set it as the same as your normal superuser password.
**Note**: You do NOT need to use `sudo` when running any Homebrew commands, and it likely won't work if you do.
Now you want to Python, pip, git, and MySQL (macOS actually ships with some of these, but we want to have the latest versions). We'll also install Redis, though this package is only relevant when testing the production environment. To do so, run the following command in your terminal:
`brew install python git mysql redis`
Now you're ready to set up the Roomlist repository on your machine.
#### Git Setup
Now, we're going to clone down a copy of the Roomlist codebase from git.gmu.edu, the SRCT code respository.
Configure your ssh keys by following the directions at git.gmu.edu/help/ssh/README.
Now, on your computer, navigate to the directory in which you want to download the project (perhaps one called development/ or something similar), and run
`git clone git@git.gmu.edu:srct/roomlist.git`
### The Virtual Environment
......@@ -120,13 +141,15 @@ Run `python manage.py makemigrations` to create the tables and rows and columns
Then run `python manage.py migrate` to execute that sql code and set up your database. Migrations also track how you've changed your models over the life of your database, which allows you to make changes to your tables without screwing up existing information.
Finally, run `python manage.py createsuperuser` to create an admin account, using the same username and email as you'll access through CAS. This means your 'full' email address, for instance gmason@masonlive.gmu.edu. Your password will be handled through CAS, so you can just use 'password' here.
Finally, run `python manage.py createsuperuser` to create an admin account, using the same username and email as you'll access through CAS. This means your 'full' email address, for instance *gmason@masonlive.gmu.edu*. Your password will be handled through CAS, so you can just use 'password' here.
(If you accidentally skip this step, you can run `python manage.py shell` and edit your user from there. Selectyour user, and set .is_staff and .is_superuser to True, then save.)
## Loading in demo data
## Loading in initial data
The project includes a json file to load majors into the database. Run `python manage.py loaddata accounts/major_fixtures.json`. You'll see output saying 'Installed 79 objects from 1 fixture(s) if all goes smoothly.
The project includes a number of json files with demo data to load into the database, for Majors and also for all freshman housing. Run `python manage.py loaddata accounts/major_fixtures.json`. You'll see output saying 'Installed 79 objects from 1 fixture(s) if all goes smoothly. Follow this with `python manage.py loaddata housing/initial_data.json` and a sizeable number of housing objects should be 'installed'.
To add all freshman housing, with the virtual environment enabled, run `python manage.py shell < housing/housing_obj_creator.py`. It will take a couple of minutes, but this script will create every building, floor, and room in your database.
## Starting search
......
......@@ -3,11 +3,12 @@ Django==1.8.9
argparse==1.2.1
django-allauth==0.20.0
django-analytical==0.19.0
django-autoslug==1.7.2
django-autoslug==1.9.3
django-braces==1.4.0
git+https://github.com/kstateome/django-cas.git
django-crispy-forms==1.4.0
django-filter==0.12.0
git+https://github.com/kstateome/django-cas.git
django-gravatar2==1.1.4
django-haystack==2.3.1
django-localflavor==1.0
......
# standard library imports
from __future__ import absolute_import, print_function
from datetime import datetime, timedelta
# core django imports
from django.core.urlresolvers import reverse
from django.views.generic import FormView
from django.contrib import messages
from django.http import HttpResponseRedirect
# third party imports
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.socialaccount.views import ConnectionsView
from allauth.socialaccount.forms import DisconnectForm
from allauth.exceptions import ImmediateHttpResponse
from braces.views import LoginRequiredMixin
class AccountAdapter(DefaultSocialAccountAdapter):
"""A custom implementation of a portion of the allauth social media package.
# the request processed by the adapter is one from the successful oauth callback
We're overriding a number of aspects of the allauth account adapter to support
our special use of the package. We are using CAS, not Django's built-in
authentication. Accordingly we change the where directed when successfully
connecting an account and how errors are dealt with. Additionally, we are not using
the social media accounts to verify or overwrite any aspect of the User model.
"""
#def pre_social_login(self, request, sociallogin):
#print(request.get_full_path(), 'pre_login')
# the request processed by the adapter is one from the successful oauth callback
# uncomment this method to print what URL you are arriving from
# def pre_social_login(self, request, sociallogin):
# print(request.get_full_path(), 'pre_login')
def populate_user(self, request, sociallogin, data):
# we don't actually want to overwrite anything from the
# social media account user
# This is a hook to populate User attributes, but we expressly don't actually
# want to overwrite anything from the social media account user. It's intended
# in the package for when you are using social media for login.
user = sociallogin.user
return user
def get_connect_redirect_url(self, request, socialaccount):
# where the user is sent if the social account is indeed authenticated
assert request.user.is_authenticated()
#print(request.get_full_path())
#if 'welcome' in request.get_full_path():
# ergo, we go with more of an approximation (at least for now)
# we are approximating that if a user has not completed the welcome walkthough,
# it is likely the page on which they started-- see the pre_social_login method
if not request.user.student.completedSocial:
return reverse('welcomeSocial', kwargs={
'slug': request.user.username,
})
else:
return reverse('updateStudent', kwargs={
return reverse('update_student', kwargs={
'slug': request.user.username,
})
def authentication_error(self, request, provider_id, error=None, exception=None,
extra_context=None):
"""Adds a custom message to the message queue if social media auth fails."""
error_message = """Looks like something went awry with your social
authentication. Wait a moment and try your username and
......@@ -52,6 +58,8 @@ class AccountAdapter(DefaultSocialAccountAdapter):
sending an email to roomlist@lists.srct.gmu.edu."""
if not request.user.student.completedSocial:
# as a reminder, here is how django handles messages
# https://docs.djangoproject.com/en/1.8/ref/contrib/messages/
messages.add_message(request, messages.ERROR, error_message)
social_redirect = HttpResponseRedirect(reverse('welcomeSocial', kwargs={
'slug': request.user.username,
......@@ -59,20 +67,25 @@ class AccountAdapter(DefaultSocialAccountAdapter):
raise ImmediateHttpResponse(social_redirect)
else:
messages.add_message(request, messages.ERROR, error_message)
update_redirect = HttpResponseRedirect(reverse('updateStudent', kwargs={
update_redirect = HttpResponseRedirect(reverse('update_student', kwargs={
'slug': request.user.username,
}))
}))
raise ImmediateHttpResponse(update_redirect)
class RemoveSocialConfirmationView(LoginRequiredMixin, ConnectionsView):
"""To customize where users are sent when removing their social media connections.
We have written our own template to handle this feature that is much prettier than
the one provided by allauth."""
template_name = "remove_social.html"
login_url = 'login'
def get(self, request, *args, **kwargs):
if not request.user.socialaccount_set.all():
# no social media accounts? back to the settings page with you!
return HttpResponseRedirect(reverse('updateStudent',
kwargs={'slug':self.request.user.username}))
return HttpResponseRedirect(reverse('update_student',
kwargs={'slug': self.request.user.username}))
else:
return super(RemoveSocialConfirmationView, self).get(request, *args, **kwargs)
......@@ -80,5 +93,5 @@ class RemoveSocialConfirmationView(LoginRequiredMixin, ConnectionsView):
return super(RemoveSocialConfirmationView, self).form_valid(form)
def get_success_url(self):
return reverse('updateStudent',
kwargs={'slug':self.request.user.username})
return reverse('update_student',
kwargs={'slug': self.request.user.username})
......@@ -5,6 +5,7 @@ from django.contrib import admin
# imports from your apps
from .models import Student, Major, Confirmation
class StudentAdmin(admin.ModelAdmin):
list_display = ("get_name", "room", "privacy", "major", "created")
......
......@@ -4,7 +4,6 @@ from __future__ import absolute_import, print_function
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
from django.contrib import messages
# third party imports
import requests
# imports from your apps
......
......@@ -13,6 +13,7 @@ from housing.models import Building, Floor, Room
class SelectRoomWidget(forms.widgets.Select):
"""A series of dropdowns in which a student can filter through housing options."""
template_name = 'room_select_widget.html'
......@@ -23,12 +24,12 @@ class SelectRoomWidget(forms.widgets.Select):
print("Sorry about that, but we're currently ignoring your fancy attrs.")
# should probably type check the other fields too
if rooms is None:
self.rooms = Room.objects.all()
self.rooms = Room.objects.all().prefetch_related('floor')
else:
if not all(isinstance(thing, Room) for thing in rooms):
raise TypeError("Rooms in a SelectRoomWidget must all be Rooms!")
if floors is None:
self.floors = Floor.objects.all()
self.floors = Floor.objects.all().prefetch_related('building')
if buildings is None:
self.buildings = Building.objects.all()
if neighborhoods is None:
......@@ -47,6 +48,7 @@ class SelectRoomWidget(forms.widgets.Select):
class SelectRoomField(forms.models.ModelChoiceField):
"""A special field for room selection, using the room selection widget."""
widget = SelectRoomWidget
# should raise error if user hasn't actually selected room, made it to end of selectors
......@@ -54,6 +56,7 @@ class SelectRoomField(forms.models.ModelChoiceField):
class BooleanRadioField(forms.TypedChoiceField):
"""Displays booleans as a radio selector, rather than checkboxes."""
def __init__(self, *args, **kwargs):
boolean_choices = ((True, 'Yes'), (False, 'No'))
......@@ -84,22 +87,21 @@ class StudentUpdateForm(forms.Form):
major = forms.ModelChoiceField(queryset=Major.objects.all(), required=False)
graduating_year = forms.IntegerField()
def clean(self):
cleaned_data = super(StudentUpdateForm, self).clean()
form_room = cleaned_data.get('room')
if not(form_room is None):
students_in_room = Student.objects.filter(room=form_room).count()
#print(students_in_room)
# print(students_in_room)
# like in bookshare, I have no idea why the form errors don't display.
if students_in_room > 12:
raise ValidationError(_('Too many students in room (%d).' % students_in_room), code='invalid')
def is_valid(self):
# errors are not printed in form.as_p?
#print("In is_valid.")
#print(self.is_bound, 'is bound')
#print(self.errors, type(self.errors), 'errors')
# print("In is_valid.")
# print(self.is_bound, 'is bound')
# print(self.errors, type(self.errors), 'errors')
valid = super(StudentUpdateForm, self).is_valid()
#print(valid)
# print(valid)
return valid
This diff is collapsed.
# python manage.py shell < accounts/major_slug_update.py
# this script updates slugs from a previous release of django from random strings
# to a slugified version of the object's name, e.g. the name of the major
# standard library imports
from __future__ import absolute_import, print_function, unicode_literals
from accounts.models import Major
majors = Major.objects.all()
for major in majors:
print(major.name)
print(major.slug)
major.save()
print(major.slug)
from django.core.management.base import BaseCommand, CommandError
from accounts.models import Student
from django.core.management.base import BaseCommand
from accounts.models import Student
class Command(BaseCommand):
args = ""
help = "Deletes all students' rooms at the end of the semester"
def handle(self, *args, **kwargs):
count = 0
for student in Student.objects.all():
student.room = None
count += 1
count += 1
self.stdout.write("Successfully overwrote %d student room(s)." % count)
# standard library imports
from __future__ import absolute_import, print_function
from __future__ import absolute_import, print_function, division
import hashlib
from datetime import date
# core django imports
......@@ -8,8 +8,6 @@ from django.utils import timezone
from model_utils.models import TimeStampedModel
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.utils.text import slugify
from django.contrib import messages
# third party imports
from autoslug import AutoSlugField
from randomslugfield import RandomSlugField
......@@ -23,7 +21,11 @@ class Major(TimeStampedModel):
name = models.CharField(max_length=50)
# I believe the longest is "Government and International Politics"
slug = AutoSlugField(populate_from='name', unique=True)
slug = AutoSlugField(populate_from='name', always_update=True, unique=True)
# always_update is set to support migrating from previous versions' slugs
# which were originally random characters
# on always_update, the slug is modified whenever the populated_from field changes
# to update from previous versions, call .save() on all existing models
def first_letter(self):
return self.name and self.name[0] or ''
......@@ -35,16 +37,19 @@ class Major(TimeStampedModel):
return unicode(self.name)
def get_absolute_url(self):
return reverse('detail_major', kwargs={
'slug': self.slug,
'major': slugify(self.name),
})
return reverse('detail_major', kwargs={'slug': self.slug})
class Meta:
ordering = ['name']
class StudentQuerySet(models.query.QuerySet):
"""Set theory defining groups of students based on their housing locations.
Used in determining privacy."""
# allows calling .floor or .building or .students when referencing a students'
# privacy to simplify life syntactically
def floor(self):
return self.filter(privacy='floor')
......@@ -72,6 +77,10 @@ class StudentQuerySet(models.query.QuerySet):
return list(floor) + list(set(building_students) - set(floor))
def visible(self, student, housing):
"""Returns a list of students visible to the student reviewing a housing object.
Example usage:
Student.objects.visible(request.user.student, floor)"""
if isinstance(housing, Room):
rooms = [housing]
elif isinstance(housing, Floor):
......@@ -95,8 +104,8 @@ class StudentQuerySet(models.query.QuerySet):
class StudentManager(models.Manager):
# this 'duplication' allows for queryset chaining
# https://docs.djangoproject.com/en/1.8/topics/db/managers/
def get_queryset(self):
return StudentQuerySet(self.model, using=self._db)
......@@ -173,9 +182,9 @@ class Student(TimeStampedModel):
original_first_name = models.CharField(max_length=100, blank=True)
original_last_name = models.CharField(max_length=100, blank=True)
# social media accounts
# welcome walkthrough completion
# each of these booleans is toggled when a student submits the form
# on the associated page
completedName = models.BooleanField(default=False)
completedPrivacy = models.BooleanField(default=False)
completedMajor = models.BooleanField(default=False)
......@@ -186,7 +195,7 @@ class Student(TimeStampedModel):
objects = StudentManager()
# this doesn't take into account superseniors or graduate students or negative values
# hence private method
# hence private method; not yet suggested for use
def _get_class(self):
time_to_graduate = self.graduating_year - self.current_year
if time_to_graduate >= 4:
......@@ -201,17 +210,18 @@ class Student(TimeStampedModel):
return "magic"
def recent_changes(self):
# part of TimeStampedModel
# timezone.now takes into account timezones, which a local machine may not
now = timezone.now()
# part of TimeStampedModel
created = self.created
# could make this more formal with dateutil, but...
days = (now - created).days
# must be int-- floor function
third_years = (days / (30 * 4)) + 1
third_years = (days // (30 * 4)) + 1
return (self.times_changed_room / third_years)
return (self.times_changed_room // third_years)
def get_floor(self):
try:
......@@ -228,6 +238,7 @@ class Student(TimeStampedModel):
return None
def totally_done(self):
"""To assess if a user has completed the welcome walkthrough."""
if self.completedName and self.completedPrivacy and self.completedMajor and self.completedSocial:
return True
else:
......@@ -235,7 +246,7 @@ class Student(TimeStampedModel):
def profile_image_url(self):
fb_uid = SocialAccount.objects.filter(user=self.user.id, provider='facebook')
#print("profile_image")
# print("profile_image")
if len(fb_uid) > 0:
return "https://graph.facebook.com/{}/picture?width=175&height=175".format(fb_uid[0].uid)
......@@ -248,25 +259,28 @@ class Student(TimeStampedModel):
def get_flag_count(self):
my_flag_num = Confirmation.objects.filter(student=self, lives_there=False).count()
return my_flag_num
# displays the student's username if the student if they choose to delete their name
def get_first_name_or_uname(self):
if not(self.user.get_short_name()):
return self.user.username
else:
return self.user.get_short_name()
def get_last_name_or_uname(self):
if not(self.user.last_name):
return self.user.username
else:
return self.user.last_name
def get_full_name_or_uname(self):
if not(self.user.get_full_name()):
return self.user.username
else:
return self.user.get_full_name()
# how recently has the student joined roomlist? changes some messages displayed
def is_noob(self):
now = timezone.now()
days = (now - self.created).days
......@@ -283,16 +297,18 @@ class Student(TimeStampedModel):
def __unicode__(self):
return unicode(self.user.username)
# uncomment if there's something going awry while saving
# def save(self, *args, **kwargs):
#print('we be savin\'!')
#from django.db.models.signals import pre_save, post_save
#for signal in [pre_save, post_save]:
#print(signal, signal.receivers)
#super(Student, self).save(*args, **kwargs)
# print('we be savin\'!')
# from django.db.models.signals import pre_save, post_save
# for signal in [pre_save, post_save]:
# print(signal, signal.receivers)
# super(Student, self).save(*args, **kwargs)
class Confirmation(TimeStampedModel):
"""Tracks relations between two students in crowdsourcing the room validity."""
confirmer = models.ForeignKey(Student, related_name='confirmer_set')
student = models.ForeignKey(Student, related_name='student_set')
......
bug_reporting = """Welcome back to SRCT Roomlist. This project is the
<a href="https://srct.gmu.edu/projects/">collaborative work
of students like you</a>. If you see anything amiss, or have ideas for
features or a better user experience, please send an email to
roomlist@lists.srct.gmu.edu, tweet
<a href="https://twitter.com/MasonSRCT/">@MasonSRCT</a>, or, for the
more technically experienced, review our
<a href="https://git.gmu.edu/srct/roomlist/issues">issues page</a>."""
privacy_reminder = """Welcome back to SRCT Roomlist. A friendly reminder you can change
your privacy settings at any time on your settings page by
clicking the cog in the upper right of your screen."""
disclaimer = """Welcome back to SRCT Roomlist. Just to be perfectly clear, this project
is provided as a service by the
<a href="https://gmu.collegiatelink.net/organization/srct">registered
student organization</a>
<a href="https://srct.gmu.edu/">Student-Run Computing and Technology</a>.
We are not a part of <a href="http://housing.gmu.edu/">Mason Housing</a>:
all information is voluntarily provided by participating students."""
whatsopen_plug = """Welcome back to SRCT Roomlist. Wondering what's open at this hour?
Check out another one of our
<a href="https://srct.gmu.edu/projects/">student-built and hosted</a>
projects:
<a href="https://whatsopen.gmu.edu/">whatsopen.gmu.edu</a>."""
open_source = """Welcome back to SRCT Roomlist. For the curious at heart,
<a href="http://www.gnu.org/philosophy/free-sw.en.html">you can always
review</a> this project's
<a href="https://git.gmu.edu/srct/roomlist/tree/master">source code</a>.
Come <a href="https://srct.gmu.edu/">to a meeting</a> and learn how to
contribute!"""
return_messages = [bug_reporting, privacy_reminder, disclaimer,
whatsopen_plug, open_source]
......@@ -17,30 +17,30 @@
<h1><strong>{{ student.get_full_name_or_uname }}</strong></h1>
{% if shares %}
<p class="lead"><strong>
{% if not student.on_campus %}
{{ student.get_first_name_or_uname }} doesn't live on campus.
{% elif student.room == None %}
{{ student.get_first_name_or_uname }} hasn't set their room yet.
{% else %}
<a href="{{ student.room.get_absolute_url }}">{{ student.room }}</a>
{% if same_floor %}
{% if has_flagged %}
<a href="{% url 'deleteConfirmation' student.slug my_flag.slug %}">
<small><span style="padding-left:15px" class="fa-stack fa-lg">
<i class="fa fa-flag fa-stack-1x"></i>
<i class="fa fa-ban fa-stack-2x text-danger"></i>
</span></small>
</a>
{% else %}
<a href="{% url 'createConfirmation' student.slug %}">
<i style="padding-left:15px" class="fa fa-flag"></i>
</a>
{% if not student.on_campus %}
{{ student.get_first_name_or_uname }} doesn't live on campus.
{% elif student.room == None %}
{{ student.get_first_name_or_uname }} hasn't set their room yet.
{% else %}
<a href="{{ student.room.get_absolute_url }}">{{ student.room }}</a>
{% if same_floor %}
{% if has_flagged %}
<a href="{% url 'deleteConfirmation' student.slug my_flag.slug %}">
<small><span style="padding-left:15px" class="fa-stack fa-lg">
<i class="fa fa-flag fa-stack-1x"></i>
<i class="fa fa-ban fa-stack-2x text-danger"></i>
</span></small>
</a>
{% else %}
<a href="{% url 'createConfirmation' student.slug %}">
<i style="padding-left:15px" class="fa fa-flag"></i>
</a>
{% endif %}
{% endif %}
{% endif %}
{% endif %}
</strong></p>
{% if student.get_flag_count > 4 %}
<p><em>* a number of other floormates say this room info is incorrect</em></p>
<p><em>* a number of other floormates say this room info is incorrect</em></p>
{% endif %}
{% endif %}
{% if student.on_campus %}
......@@ -62,7 +62,7 @@
<tr>
<td><h4><strong>Major</strong>:
{% if student.major %}