Skip to content
This repository has been archived by the owner on Jan 15, 2019. It is now read-only.

Project Listing Page #35

Merged
merged 10 commits into from
Mar 17, 2018
14 changes: 14 additions & 0 deletions acmwebsite/controllers/project.py
Original file line number Diff line number Diff line change
@@ -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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will there ever be the possibility of having a page for separate projects? If yes, the OO style thing we did with the meetings/surverys might be a good choice.

Not priority... something we eventually might consider. Can be done way after this has been merged...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. That's why I had this as a separate controller. It's kinda dumb right now, but in the future, I can definitely see eventually having a page for each project (for example, it may include more details about the project and/or additional media such as screenshots). I think is should be pretty easy to add on a ProjectController for the individual projects with this setup.

"""Root controller for listing all projects"""

@expose('acmwebsite.templates.projects')
def index(self):
return dict(page='projects', projects=DBSession.query(Project).all())
2 changes: 2 additions & 0 deletions acmwebsite/controllers/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -51,6 +52,7 @@ class RootController(BaseController):
schedule = ScheduleController()
error = ErrorController()
contact = ContactController()
projects = ProjectsController()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Individual projects should probably be located at acm.mines.edu/p/### (users are /u/***, surveys are /s/###, etc). Is there a way to do that while keeping the /projects list page? Adding an additional ProjectController?

Copy link
Member Author

@sumnerevans sumnerevans Jan 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR doesn't deal with individual project pages, so I think this is a consideration for #40. Personally, I don't like the short URLs. I think they are ugly. I've added this to the Considerations section on #40.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's continue use of short URLs even if Sumner does not like it... better for Emails, and looks better too.

Sorry Sumner.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jackrosenthal, fine by me. Still doesn't matter here, this PR only deals with the project listing page. From what I can see, we agree on this URL scheme:

  • /projects - shows a list of all the projects with links to the individual projects
  • /p/<project_id> - shows the project with id project_id

I think the real question is how to implement this. I think worst case scenario we have to add a ProjectLookupController and ProjectController in addition to the ProjectsController. Alternatively, we could expose the /projects endpoint on the root controller which would avoid that mess. My only concern with that is that it clutters up the root controller.

CC: @samsartor

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was mainly thinking about future work on #40. Maybe we should eventually rename ProjectsController to ProjectListController so that it does not get confused with the incoming ProjectController and Project[s|Lookup]Controller. I'm feeling confused just listing them.


def _before(self, *args, **kw):
tmpl_context.project_name = "acmwebsite"
Expand Down
6 changes: 5 additions & 1 deletion acmwebsite/lib/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ def markdown(*args, strip_par=False, **kwargs):


def icon(icon_name):
return Markup('<i class="glyphicon glyphicon-%s"></i>' % icon_name)
return Markup('<i class="glyphicon glyphicon-{}"></i>'.format(icon_name))


def fa_icon(icon_name):
return Markup('<i class="fa fa-{}"></i>'.format(icon_name))


def fa_icon(icon_name):
Expand Down
16 changes: 15 additions & 1 deletion acmwebsite/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
)
1 change: 1 addition & 0 deletions acmwebsite/model/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 '<User: name=%s, display=%s>' % (
Expand Down
32 changes: 32 additions & 0 deletions acmwebsite/model/project.py
Original file line number Diff line number Diff line change
@@ -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))
69 changes: 45 additions & 24 deletions acmwebsite/public/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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 */
Expand All @@ -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;
Expand Down
44 changes: 22 additions & 22 deletions acmwebsite/public/js/mailinglist.js
Original file line number Diff line number Diff line change
@@ -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);
32 changes: 16 additions & 16 deletions acmwebsite/public/js/site.js
Original file line number Diff line number Diff line change
@@ -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() }
});
});
});
1 change: 1 addition & 0 deletions acmwebsite/templates/master.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<ul class="nav navbar-nav">
<li class="${('', 'active')[value_of('page') == 'index']}"><a href="${tg.url('/')}">Home</a></li>
<li class="${('', 'active')[value_of('page') == 'schedule']}"><a href="${tg.url('/schedule')}">Schedule</a></li>
<li class="${('', 'active')[value_of('page') == 'projects']}"><a href="${tg.url('/projects')}">Projects</a></li>
<li class="${('', 'active')[value_of('page') == 'mailinglist']}"><a href="${tg.url('/mailinglist')}">Mailing List</a></li>
<li class="${('', 'active')[value_of('page') == 'contact']}"><a href="${tg.url('/contact')}">Contact</a></li>
</ul>
Expand Down
50 changes: 50 additions & 0 deletions acmwebsite/templates/projects.xhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<html py:strip=""
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">

<py:extends href="master.xhtml" />

<head py:block="head" py:strip="True">
<title>Mines ACM - Projects</title>
</head>

<body py:block="body" py:strip="True">
<h1 class="page-header">Projects</h1>
<div py:for="project in projects" class="project-list-item panel panel-default">
<div class="panel-heading">
<h2 py:content="project.name" class="panel-title project-title" />
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-3">
<img py:if="project.image" src="${project.image.url}" class="project-image" />
</div>

<div class="col-md-9">
<ul py:if="project.repository or project.website or project.video"
class="list-inline project-links">
<li py:if="project.repository">
<a href="${project.repository}" target="_blank">${h.fa_icon('code-fork')} Repository</a>
</li>
<li py:if="project.website">
<a href="${project.website}" target="_blank">${h.fa_icon('globe')} Website</a>
</li>
<li py:if="project.video_url">
<a href="${project.video_url}" target="_blank">${h.fa_icon('video-camera')} Video</a>
</li>
</ul>
<p py:content="h.markdown(project.description)" />
<p>
<b>Contributors:</b>
<ul>
<li py:for="tm in project.team_members">
<a href="${tg.url('/u/{}'.format(tm.user_name))}" py:content="tm.display_name" />
</li>
</ul>
</p>
</div>
</div>
</div>
</div>
</body>
</html>
Loading