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 + + + +

Projects

+ + + 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')