diff --git a/acmwebsite/controllers/project.py b/acmwebsite/controllers/project.py
new file mode 100644
index 0000000..0dff768
--- /dev/null
+++ b/acmwebsite/controllers/project.py
@@ -0,0 +1,14 @@
+"""Project controller module."""
+
+from tg import expose
+
+from acmwebsite.lib.base import BaseController
+from acmwebsite.model import DBSession, Project
+
+
+class ProjectsController(BaseController):
+ """Root controller for listing all projects"""
+
+ @expose('acmwebsite.templates.projects')
+ def index(self):
+ return dict(page='projects', projects=DBSession.query(Project).all())
diff --git a/acmwebsite/controllers/root.py b/acmwebsite/controllers/root.py
index c06260f..375244f 100644
--- a/acmwebsite/controllers/root.py
+++ b/acmwebsite/controllers/root.py
@@ -22,6 +22,7 @@
from acmwebsite.controllers.meeting import MeetingsController
from acmwebsite.controllers.schedule import ScheduleController
from acmwebsite.controllers.survey import SurveysController
+from acmwebsite.controllers.project import ProjectsController
from sqlalchemy.sql import functions
@@ -51,6 +52,7 @@ class RootController(BaseController):
schedule = ScheduleController()
error = ErrorController()
contact = ContactController()
+ projects = ProjectsController()
def _before(self, *args, **kw):
tmpl_context.project_name = "acmwebsite"
diff --git a/acmwebsite/lib/helpers.py b/acmwebsite/lib/helpers.py
index 75807f0..5b62067 100644
--- a/acmwebsite/lib/helpers.py
+++ b/acmwebsite/lib/helpers.py
@@ -26,7 +26,11 @@ def markdown(*args, strip_par=False, **kwargs):
def icon(icon_name):
- return Markup('' % icon_name)
+ return Markup(''.format(icon_name))
+
+
+def fa_icon(icon_name):
+ return Markup(''.format(icon_name))
def fa_icon(icon_name):
diff --git a/acmwebsite/model/__init__.py b/acmwebsite/model/__init__.py
index 2685418..bd4e0a4 100644
--- a/acmwebsite/model/__init__.py
+++ b/acmwebsite/model/__init__.py
@@ -62,6 +62,20 @@ def init_model(engine):
from acmwebsite.model.meeting import Meeting
from acmwebsite.model.mailmessage import MailMessage
from acmwebsite.model.survey import Survey, SurveyField, SurveyResponse, SurveyData
+from acmwebsite.model.project import Project, team_table
from acmwebsite.model.banner import Banner
-__all__ = ('User', 'Group', 'Permission', 'Meeting', 'MailMessage', 'Survey', 'SurveyField', 'SurveyResponse', 'SurveyData', 'Banner')
+__all__ = (
+ 'User',
+ 'Group',
+ 'Permission',
+ 'Meeting',
+ 'MailMessage',
+ 'Survey',
+ 'SurveyField',
+ 'SurveyResponse',
+ 'SurveyData',
+ 'Banner',
+ 'Project',
+ 'team_table',
+)
diff --git a/acmwebsite/model/auth.py b/acmwebsite/model/auth.py
index 0f0ffc1..c7577c1 100644
--- a/acmwebsite/model/auth.py
+++ b/acmwebsite/model/auth.py
@@ -95,6 +95,7 @@ class User(DeclarativeBase):
created = Column(DateTime, default=datetime.now)
officer_title = Column(Unicode(255), nullable=True)
profile_pic = Column(UploadedFileField)
+ projects = relation('Project', secondary='team', back_populates='team_members')
def __repr__(self):
return '' % (
diff --git a/acmwebsite/model/project.py b/acmwebsite/model/project.py
new file mode 100644
index 0000000..353265d
--- /dev/null
+++ b/acmwebsite/model/project.py
@@ -0,0 +1,32 @@
+"""Project model module."""
+
+from sqlalchemy.orm import relationship
+from sqlalchemy import Table, ForeignKey, Column, UniqueConstraint
+from sqlalchemy.types import Integer, Unicode
+
+from depot.fields.sqlalchemy import UploadedFileField
+from depot.fields.specialized.image import UploadedImageWithThumb
+
+from acmwebsite.model import DeclarativeBase, metadata, User
+
+team_table = Table(
+ 'team',
+ metadata,
+ Column('user_id', Integer(), ForeignKey('tg_user.user_id'), nullable=False),
+ Column('project_id', Integer(), ForeignKey('projects.id'), nullable=False),
+ UniqueConstraint('user_id', 'project_id'),
+)
+
+
+class Project(DeclarativeBase):
+ __tablename__ = 'projects'
+
+ # Fields
+ id = Column(Integer, autoincrement=True, primary_key=True)
+ team_members = relationship(User, secondary=team_table, back_populates='projects')
+ name = Column(Unicode(1024), unique=True, nullable=False)
+ description = Column(Unicode(4096))
+ website = Column(Unicode(512))
+ repository = Column(Unicode(512))
+ video_url = Column(Unicode(512))
+ image = Column(UploadedFileField(upload_type=UploadedImageWithThumb))
diff --git a/acmwebsite/public/css/style.css b/acmwebsite/public/css/style.css
index a52d34c..d6930d6 100644
--- a/acmwebsite/public/css/style.css
+++ b/acmwebsite/public/css/style.css
@@ -31,31 +31,31 @@
/* Footer styling */
.footer {
- margin-top: 45px;
- padding: 35px 30px 36px;
- border-top: 1px solid #e5e5e5;
+ margin-top: 45px;
+ padding: 35px 30px 36px;
+ border-top: 1px solid #e5e5e5;
}
.footer p {
- margin-bottom: 0;
- color: #555;
+ margin-bottom: 0;
+ color: #555;
}
/* Mailing List Stuff */
.ml-fullname-warn {
- display: none;
+ display: none;
}
.emailtext {
- padding: 0;
- color: inherit;
- background-color: inherit;
- border: none;
- border-radius: inherit;
+ padding: 0;
+ color: inherit;
+ background-color: inherit;
+ border: none;
+ border-radius: inherit;
}
.navbar-right {
- margin-right: 0px;
+ margin-right: 0;
}
/* Logo */
@@ -67,40 +67,40 @@
/* Contact Page */
.acm-officer-info {
- margin-bottom: 15px;
+ margin-bottom: 15px;
}
.acm-officer-position {
- font-style: italic;
+ font-style: italic;
}
.acm-officer-img {
- width: 100%;
- border-radius: 8px;
+ width: 100%;
+ border-radius: 8px;
}
/* Homepage */
.home-banner {
- margin: 35px 0 30px;
- width: 100%;
+ margin: 35px 0 30px;
+ width: 100%;
}
.home-banner-img {
- width: 100%;
- height: auto;
- border-radius: 8px;
+ width: 100%;
+ height: auto;
+ border-radius: 8px;
}
.homepage-panels {
- margin-top: 35px;
+ margin-top: 35px;
}
.checkbox.single-checkbox label {
- font-weight: bold;
+ font-weight: bold;
}
.on-first-time {
- display: none;
+ display: none;
}
/* Survey Page */
@@ -114,6 +114,27 @@
font-weight: bold;
}
+/* Project Page */
+.project-list-item .project-image {
+ border-radius: 8px;
+ width: 100%;
+ max-width: 350px;
+ max-height: 350px;
+ margin-bottom: 10px;
+}
+
+.project-list-item .project-title,
+.project-links li {
+ font-weight: bold;
+}
+
+/* Add a dot between every project link */
+.project-links li:not(:last-child)::after {
+ content: '\00B7';
+ font-size: 20px;
+ margin-left: 10px;
+}
+
/* Footer */
footer .social-links {
margin-top: 10px;
diff --git a/acmwebsite/public/js/mailinglist.js b/acmwebsite/public/js/mailinglist.js
index a766c44..33bc1ac 100644
--- a/acmwebsite/public/js/mailinglist.js
+++ b/acmwebsite/public/js/mailinglist.js
@@ -1,27 +1,27 @@
-(function($){
- var last_query = "";
- $("#ml-username").on("change keyup paste", function(){
- if ($("#ml-username").val().indexOf("@") >= 0) {
- $("#ml-username").val($("#ml-username").val().replace(/\@.*$/g, ""));
- alert("Only a Mines username is required, not a full email address.");
+(function($) {
+ var last_query = "";
+ $("#ml-username").on("change keyup paste", function() {
+ if ($("#ml-username").val().indexOf("@") >= 0) {
+ $("#ml-username").val($("#ml-username").val().replace(/\@.*$/g, ""));
+ alert("Only a Mines username is required, not a full email address.");
+ }
+ if ($("#ml-username").val() != last_query && $("#ml-username").val().length > 1) {
+ last_query = $("#ml-username").val();
+ $.getJSON("https://mastergo.mines.edu/mpapi/uid/" + encodeURIComponent($("#ml-username").val()), function(data) {
+ if (data["result"] == "success") {
+ $("#ml-fullname").val(data["attributes"]["first"] + " " + data["attributes"]["sn"]);
+ $(".ml-fullname-warn").fadeIn();
}
- if ($("#ml-username").val() != last_query && $("#ml-username").val().length > 1) {
- last_query = $("#ml-username").val();
- $.getJSON("https://mastergo.mines.edu/mpapi/uid/" + encodeURIComponent($("#ml-username").val()), function(data) {
- if (data["result"] == "success") {
- $("#ml-fullname").val(data["attributes"]["first"] + " " + data["attributes"]["sn"]);
- $(".ml-fullname-warn").fadeIn();
- }
- else {
- $("#ml-fullname").val("");
- $(".ml-fullname-warn").fadeOut();
- }
- });
+ else {
+ $("#ml-fullname").val("");
+ $(".ml-fullname-warn").fadeOut();
}
- });
+ });
+ }
+ });
- $('#mailinglist-form').submit(function() {
- $(this).find("button[type='submit']").prop('disabled',true);
- });
+ $('#mailinglist-form').submit(function() {
+ $(this).find("button[type='submit']").prop('disabled',true);
+ });
})(jQuery);
diff --git a/acmwebsite/public/js/site.js b/acmwebsite/public/js/site.js
index 7a20d95..f976fa0 100644
--- a/acmwebsite/public/js/site.js
+++ b/acmwebsite/public/js/site.js
@@ -1,22 +1,22 @@
$(document).ready(function() {
- function change_theme(theme_name) {
- $('#bs-css').attr('href', '/css/bootstrap.' + theme_name + '.min.css');
- $('#toggle-theme').text('Too ' + theme_name + '?');
- }
+ function change_theme(theme_name) {
+ $('#bs-css').attr('href', '/css/bootstrap.' + theme_name + '.min.css');
+ $('#toggle-theme').text('Too ' + theme_name + '?');
+ }
- $('#toggle-theme').click(function (event) {
- event.preventDefault();
- $.get("/toggle_theme", change_theme, "text");
- });
+ $('#toggle-theme').click(function (event) {
+ event.preventDefault();
+ $.get("/toggle_theme", change_theme, "text");
+ });
- $(".vupdate").on("change keyup paste", function() {
- $(this).attr("value", $(this).val());
- });
+ $(".vupdate").on("change keyup paste", function() {
+ $(this).attr("value", $(this).val());
+ });
- $('input[name="first_time"]').change(function() {
- checked = this.checked;
- $('.on-first-time').each(function() {
- if (checked) { $(this).fadeIn() } else { $(this).fadeOut() }
- });
+ $('input[name="first_time"]').change(function() {
+ checked = this.checked;
+ $('.on-first-time').each(function() {
+ if (checked) { $(this).fadeIn() } else { $(this).fadeOut() }
});
+ });
});
diff --git a/acmwebsite/templates/master.xhtml b/acmwebsite/templates/master.xhtml
index 12f7ec8..05afdcb 100644
--- a/acmwebsite/templates/master.xhtml
+++ b/acmwebsite/templates/master.xhtml
@@ -35,6 +35,7 @@
diff --git a/acmwebsite/templates/projects.xhtml b/acmwebsite/templates/projects.xhtml
new file mode 100644
index 0000000..04b8deb
--- /dev/null
+++ b/acmwebsite/templates/projects.xhtml
@@ -0,0 +1,50 @@
+
+
+
+
+
+ Mines ACM - Projects
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contributors:
+
+
+
+
+
+
+
+
diff --git a/migration/versions/9ecde33126dc_meetings.py b/migration/versions/9ecde33126dc_meetings.py
index da1ce90..dd2f430 100644
--- a/migration/versions/9ecde33126dc_meetings.py
+++ b/migration/versions/9ecde33126dc_meetings.py
@@ -13,15 +13,17 @@
from alembic import op
import sqlalchemy as sa
+
def upgrade():
op.create_table(
'meeting',
- sa.Column('id', sa.Integer(), primary_key=True),
- sa.Column('date', sa.DateTime(), nullable=False),
- sa.Column('location', sa.Text()),
- sa.Column('title', sa.Unicode(), nullable=False),
- sa.Column('description', sa.Unicode())
+ sa.Column('id', sa.Integer(), primary_key=True),
+ sa.Column('date', sa.DateTime(), nullable=False),
+ sa.Column('location', sa.Text()),
+ sa.Column('title', sa.Unicode(), nullable=False),
+ sa.Column('description', sa.Unicode())
)
+
def downgrade():
- op.drop_table('meeting')
\ No newline at end of file
+ op.drop_table('meeting')
diff --git a/migration/versions/d0578a57e0f9_projects.py b/migration/versions/d0578a57e0f9_projects.py
new file mode 100644
index 0000000..3a6b407
--- /dev/null
+++ b/migration/versions/d0578a57e0f9_projects.py
@@ -0,0 +1,44 @@
+"""Projects Schema
+
+Revision ID: d0578a57e0f9
+Revises: 6ba4018c2666
+Create Date: 2017-12-15 22:15:07.284502
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'd0578a57e0f9'
+down_revision = '6ba4018c2666'
+
+from alembic import op
+import sqlalchemy as sa
+
+from depot.fields.sqlalchemy import UploadedFileField
+from depot.fields.specialized.image import UploadedImageWithThumb
+
+
+def upgrade():
+ # Add the Team XREF table
+ op.create_table(
+ 'team',
+ sa.Column('user_id', sa.Integer(), sa.ForeignKey('tg_user.user_id'), nullable=False),
+ sa.Column('project_id', sa.Integer(), sa.ForeignKey('projects.id'), nullable=False),
+ sa.UniqueConstraint('user_id', 'project_id'),
+ )
+
+ # Create the actual projects table
+ op.create_table(
+ 'projects',
+ sa.Column('id', sa.Integer(), primary_key=True),
+ sa.Column('name', sa.Unicode(1024), nullable=False),
+ sa.Column('description', sa.Unicode(4096)),
+ sa.Column('website', sa.Unicode(512)),
+ sa.Column('repository', sa.Unicode(512)),
+ sa.Column('video_url', sa.Unicode(512)),
+ sa.Column('image', UploadedFileField(upload_type=UploadedImageWithThumb)),
+ )
+
+
+def downgrade():
+ op.drop_table('team')
+ op.drop_table('projects')