diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..24a9aa7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Gerard Bul-lalayao + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e191a55 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +# LAFSCMS + +A companion CMS for the Landscape Architecture Film Series website + +## Description + +_LAFSCMS_ is the companion content management system (CMS) for the _Landscape Architecture Film Series_ [website](https://l-a-f-s.org/). Currently in alpha, the plan is to merge the beta version with the [film series repo](https://github.com/ggeerraarrdd/film-series). + +Just like any real-world organization, student-run organizations such as a film series experience knowledge loss when their membership graduate. Unless there is a knowledge transfer process, that loss may lead to technical resources being underutilized or altogether becoming inactive. Alternative resources and processes always exist, but they either must be created from scratch, requiring non-zero time and resources, or are imperfect substitutes. If existing resources do the job perfectly fine, why go through all that effort? + +_LAFSCMS_ was developed to address the issue of knowledge loss by providing a CMS accessed through a user-friendly web interface to manage content and users. + +![LAFSCMS](/static/images/lafscms_1.png) + +The features of the system are based on business requirements as captured in the following user stories: + +1. "As an admin or a curator, I want to log in or log out of the CMS, so that I can access the functionalities of the CMS or stop that access." +2. "As an admin or a curator, I want to create a film series, so that I can start the process of updating the website with the new film series." +3. "As an admin or a curator, I want to edit a film series, so that information related to the film series is updated." +4. "As an admin or a curator, I want to publish a film series, so that website visitors can view information about the film series." +5. "As an admin or a curator, I want to unpublish a film series, so that the film series is removed from the website." +6. "As an admin or a curator, I want to delete a film series, so that the film series is removed from both the website and database." +7. "As an admin or a curator, I want to update a scheduled film in an ongoing series, so that website visitors are informed about new nformation." +8. "As an admin or a curator, I want to see a list of all films in a completed, ongoing or unpublished series, so that I don't duplicate a film in a future series." +9. "As an admin or a curator, I want perform CRUD operations on poster image files, so that the website and database are up-to-date in terms of the posters." +10. "As an admin or curator, I want to read documentations on using the film series website and CMS, so that I can update them." +11. "As an admin, I want to register new users, so that they can access the CMS." +12. "As an admin, I want to manage user privileges, so that users can access only the CMS functionalities based on their status and roles." + +As of v2.0.0-alpha.1, all user stories have been implemented except for #1, #6, and #10-12. + +More screenshots below. + +## Disclaimer + +ALL CONTENTS IN THIS REPO ARE FOR EDUCATIONAL PURPOSES ONLY. + +## Getting Started + +### Dependencies + +* Flask==2.3.2 +* flask_session==0.5.0 + +### Usage + +Clone it! + +```bash +git clone https://github.com/ggeerraarrdd/lafs-cms.git +``` + +Go into the project directory and run the command: + +```bash +flask run +``` + +To open the film series website, copy the URL after 'Running on'. + +To open the CMS, add `cms` at the end of the url. + +### Notes on Google Maps + +This is disabled. + +## Author(s) + +* [@ggeerraarrdd](https://github.com/ggeerraarrdd/) + +## Version History + +### Release Notes + +* See [https://github.com/ggeerraarrdd/lafs-cms/releases](https://github.com/ggeerraarrdd/lafs-cms/releases) + +### Future Work + +Development of primary features is ongoing. + +## License + +* [MIT License](https://github.com/ggeerraarrdd/large-parks/blob/main/LICENSE) + +## Acknowledgments + +* Notion AI + +## Screenshots + +![LAFSCMS](/static/images/lafscms_2.png) +![LAFSCMS](/static/images/lafscms_3.png) +![LAFSCMS](/static/images/lafscms_4.png) +![LAFSCMS](/static/images/lafscms_5.png) +![LAFSCMS](/static/images/lafscms_6.png) +![LAFSCMS](/static/images/lafscms_7.png) diff --git a/app.py b/app.py new file mode 100644 index 0000000..c28c0db --- /dev/null +++ b/app.py @@ -0,0 +1,384 @@ +import os +import json +from flask import Flask, redirect, render_template, request, session +from flask_session import Session +import pprint + +import queries +from form import get_dicts, get_query + + +# Configure application +app = Flask(__name__) + +app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 +app.config['TEMPLATES_AUTO_RELOAD'] = True + +# Configure session to use filesystem (instead of signed cookies) +app.config["SESSION_PERMANENT"] = False +app.config["SESSION_TYPE"] = "filesystem" +Session(app) + +# Set SQLite database variable +db = "lafs.db" + +# Make sure Google Maps API key is set +# if not os.environ.get("MAP_API_KEY"): +# print("INFO: MAP_API_KEY not set") +# print("INFO: Get a Google Maps API Key") +# print("INFO: On terminal, excecute: 'export MAP_API_KEY=value'") + +# raise RuntimeError("MAP_API_KEY not set") +# else: +# print("MAP_API_KEY set") +# map_api_key = os.environ.get("MAP_API_KEY") + + +@app.route("/", methods=["GET", "POST"]) +def index(): + + # Populate Bottom Container + # Website opens with list of films and their scheduled showtimes for current series + + if request.method == "POST": + return redirect("/") + + else: + + # Get info of [current] series id + query_result = queries.get_id_current_series(db) + + # Update session + session["active_series_id"] = session["current_series_id"] = query_result[0] + + # NOTE: For development purposes, change current_series_id to first series, not actual current + session["active_series_id"] = session["current_series_id"] = 1 + + # Update function variable series_id + series_id = session["active_series_id"] + + # Get info on [past] (1) series, (2) schedules, and (3) series ids + series = dict(queries.get_info_series(db, series_id)) + schedules = queries.get_info_schedules(db, series_id) + series_ids = queries.get_info_series_ids(db) + + # Print to debug + # pp = pprint.PrettyPrinter(depth=4) + # pp.pprint(schedules[0]) + + return render_template("index.html", series=series, schedules=schedules, series_ids=series_ids) + + +@app.route("/series", methods=["GET", "POST"]) +def series(): + + # Populate Bottom Container + # List of films and their scheduled showtimes for selected past series + + # Temporary bypass to session timeouts + try: + current_series_id = session["current_series_id"] + except KeyError: + current_series_id = 1 + + if request.method == "POST": + + # Get info of clicked series id + series_id = int(request.form.get("series-id")) + + if series_id == current_series_id: + return redirect("/") + else: + # Update session variable active_series_id + session["active_series_id"] = series_id + + # Get info on [past] (1) series, (2) schedules, and (3) series ids + series = dict(queries.get_info_series(db, series_id)) + schedules = queries.get_info_schedules(db, series_id) + series_ids = queries.get_info_series_ids(db) + + return render_template("index.html", series=series, schedules=schedules, series_ids=series_ids) + + else: + return redirect("/") + + +@app.route("/film", methods=["GET", "POST"]) +def film(): + + # Populate Middle Container - Right + # Individual film info for any series, current or past + + if request.method == "POST": + # Get info of [active] series id + series_id = request.form.get("series-id") + series_id = int(series_id) + + # Get film_id of requested film + film_id = request.form.get("film-id") + + # Get info on [past] (1) series, (2) schedules, and (3) series ids + series = dict(queries.get_info_series(db, series_id)) + schedules = queries.get_info_schedules(db, series_id) + series_ids = queries.get_info_series_ids(db) + + # Get info of requested film + film = queries.get_info_film(db, film_id) + + return render_template("film.html", series=series, schedules=schedules, series_ids=series_ids, film=film) + + else: + return redirect("/") + + +@app.route("/map") +def map(): + + # Populate Middle Container - Right + # Map of Plym Auditorium + + # Set map_api_key + global map_api_key + + # Get info of [active] series id + series_id = session["active_series_id"] + + # Get info on [active] (1) series, (2) schedules, and (3) series ids + series = dict(queries.get_info_series(db, series_id)) + schedules = queries.get_info_schedules(db, series_id) + series_ids = queries.get_info_series_ids(db) + + return render_template("map.html", series=series, schedules=schedules, series_ids=series_ids, map_api_key=map_api_key) + + +@app.route("/org") +def org(): + + # Populate Middle Container - Right + # Info on ORG + + # Get info of [active] series id + series_id = session["active_series_id"] + + # Get info on [active] (1) series, (2) schedules, and (3) series ids + series = dict(queries.get_info_series(db, series_id)) + schedules = queries.get_info_schedules(db, series_id) + series_ids = queries.get_info_series_ids(db) + + return render_template("org.html", series=series, schedules=schedules, series_ids=series_ids) + + +@app.route("/cms") +def cms(): + + # Get info of [active] series id + global active_series_id + series_id = active_series_id = 6 + + # Get info on [active] (1) series, (2) schedules, and (3) series ids + series = dict(queries.get_info_series(db, series_id)) + next = queries.get_info_cms_next_film(db, series_id) + films = queries.get_info_schedules(db, series_id) + series_ids = queries.get_info_series_ids(db) + + serieses = queries.get_info_serieses(db) + # series_info = queries.get_info_series_status(db, series_id) + + return render_template("cms_index.html", + current_series=series, + next=next, + films=films, + serieses=serieses, + sidebar="index") + + +@app.route("/cms/series") +def cmsseries(): + + # Get info of [active] series id + global active_series_id + series_id = active_series_id = "6" + + # Get info on [active] (1) series, (2) schedules, and (3) series ids + series = dict(queries.get_info_series(db, series_id)) + schedules = queries.get_info_schedules(db, series_id) + series_ids = queries.get_info_series_ids(db) + + serieses = queries.get_info_serieses(db) + series_info = queries.get_info_series_status(db, series_id) + + return render_template("cms_series.html", + current_series=series, + schedules=schedules, + serieses=serieses, + series_info=series_info, + sidebar="series") + + +@app.route("/cms/create", methods=["GET", "POST"]) +def cmscreate(): + + if request.method == "POST": + + post = request.form.to_dict() + + dicts = get_dicts(post) + dict_series = dicts[0] + dict_schedule = dicts[1] + dict_films = dicts[2] + dict_colors = dicts[3] + + print(json.dumps(dict_series, indent=4)) + print(json.dumps(dict_schedule, indent=4)) + print(json.dumps(dict_films, indent=4)) + print(json.dumps(dict_colors, indent=4)) + + # + # FILM SERIES + # + # 1. Translate dict into SQL query + query_series = get_query(dict_series) + # 2. Execute query + new_series_id = queries.insert_new_series(db, query_series) + + # + # FILMS + # + # 1. Translate dict into SQL query + query_films = get_query(dict_films) + # 2. Execute query + queries.insert_new_records(db, query_films) + + # + # SCHEDULE + # + # 0. Update dictionary + for film in dict_schedule: + dict_schedule[film] = {"series_id": new_series_id, **dict_schedule[film]} + for film in dict_schedule: + if "id" in dict_schedule[film]: + dict_schedule[film]["film_id"] = dict_schedule[film].pop("id") + # 1. Translate dict into SQL query + query_schedule = get_query(dict_schedule) + # 2. Execute query + queries.insert_new_records(db, query_schedule) + + # + # COLORS + # + # 0. Update dictionary + dict_colors = {"series_id": new_series_id, **dict_colors} + # 1. Translate dict into SQL query + query_colors = get_query(dict_colors) + # 2. Execute query + queries.insert_new_records(db, query_colors) + + + print(query_series) + print(query_films) + print(query_schedule) + print(query_colors) + + return redirect("/cms/series") + + else: + return render_template("cms_create.html", + sidebar="series") + + +@app.route("/cms/view", methods=["GET", "POST"]) +def cms_view(): + + if request.method == "POST": + + return redirect("/cms") + + else: + cms_series_id = request.args.get('id') + + cms_series = dict(queries.get_info_series(db, cms_series_id)) + cms_schedules = queries.get_info_cms_schedules(db, cms_series_id) + + cms_serieses = queries.get_info_serieses(db) + cms_status = next((series for series in cms_serieses if series['series_id'] == int(cms_series_id)), None) + + if cms_status['status'] == 1: + edit_status = 'disabled' + else: + edit_status = '' + + return render_template("cms_view.html", + cms_series=cms_series, + cms_schedules=cms_schedules, + cms_status=cms_status, + edit_status=edit_status, + sidebar="series") + + +@app.route("/cms/unpublish", methods=["GET", "POST"]) +def cms_unpublish(): + + if request.method == "POST": + + series_id = request.args.get('unpublish') + + print(series_id) + + return render_template("cms_unpublish.html", + sidebar="series") + + else: + + return redirect("/cms") + + +@app.route("/cms/films") +def cmsfilms(): + + films = queries.get_info_cms_films(db) + + return render_template("cms_films.html", + films=films, + sidebar="films") + + +@app.route("/cms/org") +def cmsorg(): + + users = queries.get_info_users(db) + + return render_template("cms_org.html", + users=users, + sidebar="org") + + +@app.route("/cms/register", methods=["GET", "POST"]) +def cmsregister(): + + if request.method == "POST": + + new_name_first = request.form.get("new_name_first") + new_name_last = request.form.get("new_name_last") + new_username = request.form.get("new_username") + new_password = request.form.get("new_password") + new_role = request.form.get("new_role") + + print(new_name_first) + print(new_name_last) + print(new_username) + print(new_password) + print(new_role) + + return redirect("/cms/org") + + else: + + return render_template("cms_register.html", + sidebar="org") + + +@app.route("/cms/media") +def cmsmedia(): + + return render_template("cms_media.html", + sidebar="media") diff --git a/form.py b/form.py new file mode 100644 index 0000000..206dd43 --- /dev/null +++ b/form.py @@ -0,0 +1,157 @@ +import json + + +def get_dicts(post): + + keys_series = [ + "new_series_semester", + "new_series_year", + "new_series_title", + "new_series_brief", + "new_series_poster" + ] + + keys_schedule = [ + "id", + "schedule" + ] + + keys_films = [ + "id", + "film_title", + "film_director", + "film_year", + "film_runtime", + "film_description", + "wiki", + "note" + ] + + keys_colors = [ + "color1", + "color2", + "color3" + ] + + # PART 1 + # Create series dict + dict_series = {k.lstrip('new_'): post[k] for k in keys_series if k in post} + + # INTERIM + # Process post to create dictionary for schedule and films keys + dict_all = {} + + for key, value in post.items(): + if key not in keys_series and key not in keys_colors: + num = 'film' + key[-1] + if key[-2].isdigit(): + num = 'film' + key[-2:] + key = key.rstrip('0123456789') + if num not in dict_all: + dict_all[num] = {} + key = key.rstrip('0123456789') + dict_all[num][key] = value + + # PART 2 + # Only schedule keys in dict_all + dict_schedule = {} + + for key, value in dict_all.items(): + film_values = {} + for k, v in value.items(): + if k in keys_schedule: + film_values[k] = v + dict_schedule[key] = film_values + + # PART 3 + # Only film keys in dict_all + dict_films = {} + + for key, value in dict_all.items(): + film_values = {} + for k, v in value.items(): + if k in keys_films: + film_values[k] = v + dict_films[key] = film_values + + # PART 4 + # Create films dict + dict_colors = {k: post[k] for k in keys_colors if k in post} + + # RETURN + return dict_series, dict_schedule, dict_films, dict_colors + + +def get_query(dict): + + # Translate dictionary into an INSERT query statement + + keys_series = [ + "new_series_semester", + "new_series_year", + "new_series_title", + "new_series_brief", + "new_series_poster" + ] + + keys_schedule = [ + "id", + "schedule" + ] + + keys_films = [ + "id", + "film_title", + "film_director", + "film_year", + "film_runtime", + "film_description", + "wiki", + "note" + ] + + keys_colors = [ + "color1", + "color2", + "color3" + ] + + if "film1" in dict: + if "schedule" in list(dict["film1"].keys()): + table_name = "schedules" + if "film_title" in list(dict["film1"].keys()): + table_name = "films" + + # COLUMNS + col_names = "" + + for key in dict['film1'].keys(): + col_names += key + ", " + + col_names = col_names.rstrip(", ") + + # VALUES + values = "" + + for keys_films in dict.keys(): + values += "(" + for key, value in dict[keys_films].items(): + values += f"'{value}', " + values = values.rstrip(", ") + values += "), " + + values = values.rstrip(", ") + + # FULL QUERY + query = f"INSERT INTO {table_name} ({col_names}) VALUES {values};" + else: + if "series_title" in dict: + table_name = "series" + elif 'color1' in dict: + table_name = "colors" + + columns = ", ".join(dict.keys()) + values = ", ".join(["'" + str(value) + "'" for value in dict.values()]) + query = f"INSERT INTO {table_name} ({columns}) VALUES ({values});" + + return query diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..a971e02 --- /dev/null +++ b/helpers.py @@ -0,0 +1,16 @@ +from flask import redirect, render_template, request, session + +def apology(message, code=400): + """Render message as an apology to user.""" + def escape(s): + """ + Escape special characters. + + https://github.com/jacebrowning/memegen#special-characters + """ + for old, new in [("-", "--"), (" ", "-"), ("_", "__"), ("?", "~q"), + ("%", "~p"), ("#", "~h"), ("/", "~s"), ("\"", "''")]: + s = s.replace(old, new) + return s + return render_template("apology.html", top=code, bottom=escape(message)), code + diff --git a/lafs.db b/lafs.db new file mode 100644 index 0000000..63e0139 Binary files /dev/null and b/lafs.db differ diff --git a/queries.py b/queries.py new file mode 100644 index 0000000..0d522d2 --- /dev/null +++ b/queries.py @@ -0,0 +1,340 @@ +import sqlite3 + + +def get_id_current_series(db): + """Get id of current film series.""" + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + # Query db + query = "SELECT series_id FROM series ORDER BY series_id DESC LIMIT 1;" + cursor.execute(query) + current_series_id = cursor.fetchone() + + # Close cursor and connection + cursor.close() + connection.close() + + return(current_series_id) + + +def get_info_series(db, series_id): + """Get info of series.""" + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + # Query db + query = "SELECT s.series_id, " + query = query + "series_semester || series_year AS semester, " + query = query + "series_semester, " + query = query + "series_year, " + query = query + "series_title, " + query = query + "series_brief, " + query = query + "series_poster, " + query = query + "series_poster_url, " + query = query + "series_display, " + query = query + "color1, " + query = query + "color2, " + query = query + "color3 " + query = query + "FROM series s " + query = query + "LEFT JOIN colors AS c ON s.series_id = c.series_id " + query = query + "WHERE s.series_id = ?; " + cursor.execute(query, (series_id,)) + results = cursor.fetchone() + + # Close cursor and connection + cursor.close() + connection.close() + + return(results) + + +def get_info_schedules(db, series_id): + """Get info of series schedules.""" + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + # Query db + query = "SELECT strftime('%d', schedule) AS day, " + query = query + "f.id, " + query = query + "rtrim (substr ('January February March April May June July August SeptemberOctober November December', strftime ('%m', schedule) * 9 - 8, 9)) AS month, " + query = query + "film_title, film_director, film_year, film_runtime, wiki, sc.schedule, sc.note " + query = query + "FROM series AS se " + query = query + "JOIN schedules AS sc ON se.series_id = sc.series_id " + query = query + "JOIN films AS f ON sc.film_id = f.id " + query = query + "WHERE se.series_id = ?; " + cursor.execute(query, (series_id,)) + + # Get rows + rows = cursor.fetchall() + + # Get the column names from cursor.description + columns = [column[0] for column in cursor.description] + + # Convert each row into a dictionary using zip + result = [dict(zip(columns, row)) for row in rows] + + # Close cursor and connection + cursor.close() + connection.close() + + return(result) + + +def get_info_series_ids(db): + """Get info of series ids.""" + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + # Query db + query = "SELECT DISTINCT(series_id), series_semester, series_year, series_display " + query = query + "FROM series; " + cursor.execute(query) + + # Get rows + rows = cursor.fetchall() + + # Get the column names from cursor.description + columns = [column[0] for column in cursor.description] + + # Convert each row into a dictionary using zip + result = [dict(zip(columns, row)) for row in rows] + + return(result) + + +def get_info_serieses(db): + """Get info of series ids.""" + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + # Query db + query = "SELECT " + query = query + "s.series_id " + query = query + ",series_semester || series_year AS semester " + query = query + ",series_semester AS series_semester " + query = query + ",series_year AS series_year " + query = query + ",series_title " + query = query + ",series_brief " + query = query + ",series_poster_url " + query = query + ",series_display " + query = query + ",bgcolor1 AS color_1 " + query = query + ",bgcolor2 AS color_2 " + query = query + ",text_color3 AS color_3 " + query = query + ",min(sc.schedule) AS min_date " + query = query + ",max(sc.schedule) AS max_date " + query = query + ",max(date(substr(sc.schedule, 1, 4) || '-' || substr(sc.schedule, 6, 2) || '-' || substr(sc.schedule, 9, 2))) < current_timestamp AS status " + query = query + "FROM series s " + query = query + "LEFT JOIN schedules sc ON s.series_id = sc.series_id " + query = query + "LEFT JOIN colors AS c ON s.series_id = c.series_id " + query = query + "GROUP BY s.series_id " + cursor.execute(query) + + # Get rows + rows = cursor.fetchall() + + # Get the column names from cursor.description + columns = [column[0] for column in cursor.description] + + # Convert each row into a dictionary using zip + result = [dict(zip(columns, row)) for row in rows] + + return(result) + + +def get_info_film(db, film_id): + """Get info of film.""" + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + # Query db + query = "SELECT * " + query = query + "FROM films " + query = query + "WHERE id = ?; " + cursor.execute(query, (film_id,)) + + # Get row + row = cursor.fetchone() + + # Convert row into a dictionary + result = dict(row) + + return(result) + + +def get_info_series_status(db, series_id): + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + query = "SELECT " + query = query + "max(date(substr(schedule, 1, 4) || '-' || substr(schedule, 6, 2) || '-' || substr(schedule, 9, 2))) < current_timestamp AS status " + query = query + "FROM schedules WHERE series_id = ?; " + cursor.execute(query, series_id) + + series_status = cursor.fetchone() + + # print(len(series_status)) + return(series_status) + + +def get_info_cms_next_film(db, series_id): + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + query = "SELECT " + query = query + "f.id, " + query = query + "film_title, " + query = query + "film_director, " + query = query + "film_year, " + query = query + "film_runtime, " + query = query + "wiki, " + query = query + "sc.schedule, " + query = query + "sc.note " + query = query + "FROM series AS se " + query = query + "JOIN schedules AS sc ON se.series_id = sc.series_id " + query = query + "JOIN films AS f ON sc.film_id = f.id " + query = query + "WHERE se.series_id = ? " + query = query + "AND sc.schedule >= DATE('now') " + query = query + "ORDER BY schedule " + query = query + "LIMIT 1; " + cursor.execute(query, (series_id,)) + + cms_next = cursor.fetchone() + + return(cms_next) + + +def get_info_cms_films(db): + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + query = "SELECT " + query = query + "f.id " + query = query + ", f.film_title " + query = query + ", f.film_director " + query = query + ", f.film_year " + query = query + ", f.film_runtime " + query = query + ", f.film_description " + query = query + ", f.wiki, imdb " + query = query + ", f.film_poster " + query = query + ", date(f.entry_created) AS entry_created " + query = query + ", date(f.entry_updated) AS entry_updated " + query = query + ", group_concat(s.series_id, ', ') AS series " + query = query + "FROM films f " + query = query + "JOIN schedules s ON f.id = s.film_id " + query = query + "GROUP BY f.id " + query = query + "ORDER BY film_title ASC; " + cursor.execute(query) + + cms_films = cursor.fetchall() + + return(cms_films) + + +def get_info_cms_schedules(db, series_id): + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + query = "SELECT " + query = query + "s.series_id " + query = query + ",sc.schedule " + query = query + ",sc.film_id " + query = query + ",f.film_title " + query = query + ",f.film_director " + query = query + ",f.film_year " + query = query + ",f.film_runtime " + query = query + ",f.film_description " + query = query + ",f.wiki " + query = query + ",f.imdb " + query = query + ",f.film_poster " + query = query + "FROM series s " + query = query + "JOIN schedules sc ON s.series_id = sc.series_id " + query = query + "JOIN films f ON sc.film_id = f.id " + query = query + "JOIN colors AS c ON s.series_id = c.series_id " + query = query + "WHERE s.series_id = ? " + query = query + "ORDER BY sc.schedule ASC; " + cursor.execute(query, (series_id,)) + + cms_schedules = cursor.fetchall() + + return(cms_schedules) + + +def get_info_users(db): + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + query = "SELECT * FROM users ORDER BY name_first; " + cursor.execute(query) + + results = cursor.fetchall() + + return(results) + + +def insert_new_series(db, query): + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + cursor.execute(query) + + connection.commit() + + query = "SELECT series_id FROM series WHERE rowid = ?" + cursor.execute(query, (cursor.lastrowid,)) + + new_series_id = cursor.fetchone()[0] + + connection.close() + + return(new_series_id) + + +def insert_new_records(db, query): + + # Create connection and cursor + connection = sqlite3.connect(db, check_same_thread=False) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + cursor.execute(query) + + connection.commit() + + return 1 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4150024 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Flask==2.2.5 +flask_session==0.5.0 \ No newline at end of file diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..6139cad Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/images/1_poster_anxiousterrains.jpg b/static/images/1_poster_anxiousterrains.jpg new file mode 100644 index 0000000..5e3bd79 Binary files /dev/null and b/static/images/1_poster_anxiousterrains.jpg differ diff --git a/static/images/2_poster_displacement.jpg b/static/images/2_poster_displacement.jpg new file mode 100644 index 0000000..92d5f30 Binary files /dev/null and b/static/images/2_poster_displacement.jpg differ diff --git a/static/images/3_poster_ladscapes.jpg b/static/images/3_poster_ladscapes.jpg new file mode 100644 index 0000000..be5ed47 Binary files /dev/null and b/static/images/3_poster_ladscapes.jpg differ diff --git a/static/images/4_poster_aliennation.jpg b/static/images/4_poster_aliennation.jpg new file mode 100644 index 0000000..f4f6750 Binary files /dev/null and b/static/images/4_poster_aliennation.jpg differ diff --git a/static/images/6_poster_figures.jpg b/static/images/6_poster_figures.jpg new file mode 100644 index 0000000..c0878d9 Binary files /dev/null and b/static/images/6_poster_figures.jpg differ diff --git a/static/images/film-series0_1.png b/static/images/film-series0_1.png new file mode 100644 index 0000000..11ff58c Binary files /dev/null and b/static/images/film-series0_1.png differ diff --git a/static/images/film-series0_2.png b/static/images/film-series0_2.png new file mode 100644 index 0000000..224cb13 Binary files /dev/null and b/static/images/film-series0_2.png differ diff --git a/static/images/film-series1_1.jpg b/static/images/film-series1_1.jpg new file mode 100644 index 0000000..e144760 Binary files /dev/null and b/static/images/film-series1_1.jpg differ diff --git a/static/images/film-series1_2.jpg b/static/images/film-series1_2.jpg new file mode 100644 index 0000000..77f101f Binary files /dev/null and b/static/images/film-series1_2.jpg differ diff --git a/static/images/film-series1_3.png b/static/images/film-series1_3.png new file mode 100644 index 0000000..b3a2808 Binary files /dev/null and b/static/images/film-series1_3.png differ diff --git a/static/images/film-series2_1.jpg b/static/images/film-series2_1.jpg new file mode 100644 index 0000000..3628416 Binary files /dev/null and b/static/images/film-series2_1.jpg differ diff --git a/static/images/film-series2_2.jpg b/static/images/film-series2_2.jpg new file mode 100644 index 0000000..9d15f70 Binary files /dev/null and b/static/images/film-series2_2.jpg differ diff --git a/static/images/film-series2_3.png b/static/images/film-series2_3.png new file mode 100644 index 0000000..dc2f5f3 Binary files /dev/null and b/static/images/film-series2_3.png differ diff --git a/static/images/film-series3_1.jpg b/static/images/film-series3_1.jpg new file mode 100644 index 0000000..0a9daaa Binary files /dev/null and b/static/images/film-series3_1.jpg differ diff --git a/static/images/film-series3_2.jpg b/static/images/film-series3_2.jpg new file mode 100644 index 0000000..07ad90a Binary files /dev/null and b/static/images/film-series3_2.jpg differ diff --git a/static/images/film-series3_3.png b/static/images/film-series3_3.png new file mode 100644 index 0000000..295f6c7 Binary files /dev/null and b/static/images/film-series3_3.png differ diff --git a/static/images/film-series4_1.jpg b/static/images/film-series4_1.jpg new file mode 100644 index 0000000..32f445a Binary files /dev/null and b/static/images/film-series4_1.jpg differ diff --git a/static/images/film-series4_2.png b/static/images/film-series4_2.png new file mode 100644 index 0000000..f83b877 Binary files /dev/null and b/static/images/film-series4_2.png differ diff --git a/static/images/film-series5_1.jpg b/static/images/film-series5_1.jpg new file mode 100644 index 0000000..ad34a6a Binary files /dev/null and b/static/images/film-series5_1.jpg differ diff --git a/static/images/film-series5_2.png b/static/images/film-series5_2.png new file mode 100644 index 0000000..c4598ca Binary files /dev/null and b/static/images/film-series5_2.png differ diff --git a/static/images/lafscms_1.png b/static/images/lafscms_1.png new file mode 100644 index 0000000..21e5a5d Binary files /dev/null and b/static/images/lafscms_1.png differ diff --git a/static/images/lafscms_2.png b/static/images/lafscms_2.png new file mode 100644 index 0000000..9dfc4e6 Binary files /dev/null and b/static/images/lafscms_2.png differ diff --git a/static/images/lafscms_3.png b/static/images/lafscms_3.png new file mode 100644 index 0000000..c1039f0 Binary files /dev/null and b/static/images/lafscms_3.png differ diff --git a/static/images/lafscms_4.png b/static/images/lafscms_4.png new file mode 100644 index 0000000..911f403 Binary files /dev/null and b/static/images/lafscms_4.png differ diff --git a/static/images/lafscms_5.png b/static/images/lafscms_5.png new file mode 100644 index 0000000..032cefc Binary files /dev/null and b/static/images/lafscms_5.png differ diff --git a/static/images/lafscms_6.png b/static/images/lafscms_6.png new file mode 100644 index 0000000..bb02be4 Binary files /dev/null and b/static/images/lafscms_6.png differ diff --git a/static/images/lafscms_7.png b/static/images/lafscms_7.png new file mode 100644 index 0000000..1bf9104 Binary files /dev/null and b/static/images/lafscms_7.png differ diff --git a/static/map.js b/static/map.js new file mode 100644 index 0000000..27b4208 --- /dev/null +++ b/static/map.js @@ -0,0 +1,36 @@ + +// Initialize and add the map +let map; +let map_lat = parseFloat(document.getElementById('map').getAttribute("map-lat")); +let map_lng = parseFloat(document.getElementById('map').getAttribute("map-lng")); +let map_zoom = parseFloat(document.getElementById('map').getAttribute("map-zoom")); +let map_marker_title = document.getElementById('map').getAttribute("map-marker-title"); + +async function initMap() { + // The location of Plym Auditorium + const position = { lat: map_lat, lng: map_lng }; + // Request needed libraries. + //@ts-ignore + const { Map } = await google.maps.importLibrary("maps"); + const { AdvancedMarkerView } = await google.maps.importLibrary("marker"); + + // The map, centered at Plym Auditorium + map = new Map(document.getElementById("map"), { + zoom: map_zoom, + center: position, + mapId: "plym", + mapTypeId: 'satellite', + disableDefaultUI: true, + tilt: 0, + }); + + // The marker, positioned at Plym Auditorium + const marker = new AdvancedMarkerView({ + map: map, + position: position, + title: map_marker_title, + }); +} + +initMap(); + diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..f68c429 --- /dev/null +++ b/static/styles.css @@ -0,0 +1,1223 @@ +body { + margin: 0; + padding: 0; + font-family: Verdana, Arial, Helvetica, sans-serif; + -webkit-font-smoothing: antialiased !important; + -webkit-text-size-adjust: 100%; + height: 100svh; + min-height: -webkit-fill-available; + overflow-y: hidden; +} + + +/* +* Main Container +*/ +.main-container { + margin: 0; + padding: 0; + position: absolute; + left: 0; + height: 100svh; +} + + + +/* +* Top Container +*/ +.top-container { + margin: 0; + height: 38svh; + width: 100vw; + font-weight: bold; + font-size: 11px; + position: fixed; + top: 0svh; + display: flex; +} + +.top-container-title { + margin: 0; + padding: 10px; + width: 25vw; + font-size: 18px; + overflow-wrap: break-word; +} + +.top-container-byline { + margin: 0; + padding-top: 10px; + padding-right: 20px; + padding-bottom: 10px; + padding-left: 20px; + width: 55vw; +} + +.top-container-location { + margin: 0; + padding: 10px; + width: 14vw; + white-space: nowrap; + overflow: hidden; +} + +.top-container-serieses { + margin: 0; + padding: 10px; + width: 6vw; + font-weight: 100; + line-height: 1.6em; +} + +.button-series { + padding-bottom: 2px; + background: none !important; + border: none; + cursor: pointer; + font-weight: bold; + font-size: 11px; +} + +.middle-container { + margin: 0; + padding: 0; +} + + + +/* Middle Container */ +.middle-container { + margin: 0; + padding: 0; + height: 24svh; + width: 100vw; + position: fixed; + top: 38svh; + display: flex; +} + +.middle-container-left { + margin: 0; + padding: 0; + width: 22vw; +} + +.middle-container-right { + margin: 0; + padding: 0; + width: 78vw; + display: table; +} + +.middle-container-right-index { + margin: 0; + padding-top: 0; + padding-right: 30px; + padding-bottom: 0; + padding-left: 40px; + font-size: calc(100% + 1.25vw); + line-height: calc(100% + .4vw); + font-weight: bold; + display: table-cell; + vertical-align: middle; +} + +.middle-container-right-film { + margin: 0; + padding-left: 30px; + padding-right: 30px; + padding-top: 30px; + font-size: 14px; + display: table; +} + +.middle-container-right-film-title { + font-size: 20px; + font-weight: bold; +} + +.middle-container-right-org { + margin: 0; + padding-left: 30px; + padding-right: 30px; + padding-top: 30px; + height: 22svh; + font-size: 14px; +} + +.middle-container-right-org-title { + font-size: 20px; + font-weight: bold; +} + +/* Set the size of the div element that contains the map */ +.middle-container-right { + margin: 0; + padding: 0; + height: 24svh; + width: 78vw; +} + + + +/* Bottom Container */ +.bottom-container { + margin: 0; + padding: 40px 300px 0 0; + width: 100%; + height: 38svh; + position: relative; + top: 62svh; + font-size: 15px; + display: flex; + flex-direction: row; +} + +.bottom-container-film { + margin: 0; + padding: 0; + width: 395px; +} + +.button-title { + padding: 2px 2px 2px 0px; + background: none !important; + border: none; + cursor: pointer; + font-size: 15px; + font-weight: bold; +} + +.text-left { + text-align: left !important; +} + + +/* +* Strip Container +*/ +.strip-container { + margin: 0; + padding: 0; + height: 0; + width: 0; +} + + + + + +/* +* Screen +*/ + + +@media only screen and (max-width: 1099px) { + + + .body-screen { + margin: 0; + padding: 0; + font-family: Verdana, Arial, Helvetica, sans-serif; + -webkit-font-smoothing: subpixel-antialiased !important; + -webkit-text-size-adjust: 100%; + overflow-y: auto; + } + + + + /* + * Main Container + */ + .main-container-screen { + margin: 0; + padding: 0; + height: auto; + width: auto; + position: static; + } + + + + /* + * Top Container + */ + .top-container-screen { + margin: 0; + padding: 0 0 30px 0; + height: auto; + width: auto; + font-weight: bold; + font-size: 11px; + position: static; + display: flex; + flex-direction: column; + } + + .top-container-title-screen { + padding: 10px; + height: auto; + width: auto; + font-size: 18px; + overflow-wrap: break-word; + } + + .top-container-byline-screen { + padding-top: 10px; + padding-bottom: 20px; + padding-left: 150px; + height: auto; + width: auto; + } + + .top-container-location-screen { + padding-left: 30px; + height: auto; + width: auto; + white-space: nowrap; + overflow: hidden; + } + + .top-container-serieses-screen { + padding-left: 24px; + height: auto; + width: auto; + font-weight: 100; + line-height: 1.6em; + } + + .button-series { + padding-bottom: 2px; + background: none !important; + border: none; + cursor: pointer; + font-weight: bold; + font-size: 11px; + } + + + + /* + Middle Container + */ + .middle-container-screen { + margin: 0; + padding: 0; + height: auto; + width: auto; + position: static; + display: flex; + flex-direction: column; + } + + .middle-container-left-screen { + margin: 0; + padding: 0; + height: 0; + width: 0; + } + + .middle-container-right-screen { + margin: 0; + padding: 0; + min-height: 30vh; + width: auto; + position: static; + display: table; + } + + .middle-container-right-index-screen { + margin: 0; + padding-top: 40px; + padding-right: 30px; + padding-bottom: 40px; + padding-left: 30px; + height: auto; + width: auto; + position: static; + font-size: calc(100% + 2.2vw); + line-height: calc(100% + 1vw); + font-weight: bold; + display: table-cell; + vertical-align: middle; + } + + .middle-container-right-film-screen { + margin: 0; + padding-top: 35px; + padding-right: 30px; + padding-bottom: 35px; + padding-left: 30px; + height: auto; + width: auto; + position: static; + font-size: 14px; + display: table; + } + + .middle-container-right-film-title-screen { + height: auto; + width: auto; + position: static; + font-size: 20px; + font-weight: bold; + } + + .middle-container-right-org-screen { + margin: 0; + padding-left: 30px; + padding-right: 30px; + padding-top: 30px; + height: auto; + width: auto; + position: static; + font-size: 14px; + } + + .middle-container-right-org-title-screen { + height: auto; + width: auto; + position: static; + font-size: 20px; + font-weight: bold; + } + + /* Set the size of the div element that contains the map */ + .middle-container-right-screen { + margin: 0; + padding: 0; + height: auto; + width: 100%; + } + + + + /* + Bottom Container + */ + .bottom-container-screen { + margin: 0; + padding: 40px 0 300px 150px; + height: auto; + width: auto; + position: static; + font-size: 15px; + display: flex; + flex-direction: column; + } + + .bottom-container-film-screen { + margin: 0; + padding: 0 0 50px 0; + width: auto; + } + + .button-title { + padding: 2px 2px 2px 0px; + background: none !important; + border: none; + cursor: pointer; + font-size: 15px; + font-weight: bold; + } + + .text-left { + text-align: left !important; + } + + + + /* + * Strip Container + */ + .strip-container-screen { + padding-bottom: 60px; + padding-left: 115px; + height: 200px; + width: auto; + } + + .strip-container-gif-screen { + height: 200px; + width: auto; + background-position: center; + background-size: cover; + } + + + + +} + + + + + + + + + + + + + + + + + + + + + + + + + +/* +* +* +* +* +* +* +* +* +* +* +* CMS +* +* +* +* +* +* +* +* +* +* +*/ + +.cms-body { + margin: 0; + padding: 0; + font-family: Helvetica, Verdana, Arial, sans-serif; + -webkit-font-smoothing: antialiased !important; + -webkit-text-size-adjust: 100%; + overflow-y: auto !important; + background-color: #EFEFEF; +} + +.cms-main { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + display: flex; + letter-spacing: 0.5px; + background-color: #EFEFEF; +} + +/* +* CMS - MAIN - LEFT +*/ +.cms-main-left { + margin: 0; + padding: 0; + width: 300px; + height: 100vh; + background-color: #ADB5BD; + position: fixed; + left: 0; + z-index: 1; +} + +.cms-main-left-container { + margin: 0; + padding: 0; + width: auto; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; +} + + +.cms-main-left-title { + margin: 0; + padding: 0 0 0 45px; + width: auto; + height: 75px; + font-size: 32px; + display: flex; + align-items: center; +} + +.cms-main-left-item { + margin: 0; + padding: 0; + width: auto; + height: 65px; + display: flex; + flex-direction: row; +} + +.cms-main-left-item-left { + margin: 0; + padding: 0; + width: 5px; + height: auto; +} + +.cms-main-left-item-right { + margin: 0; + padding: 0 0 0 40px; + width: 100%; + height: auto; + display: flex; + align-items: center; + letter-spacing: 1px; +} + +.cms-main-left-item-right a { + color: black; + text-decoration: none; +} + +.cms-main-left-item-left-active { + background-color: #343A40 !important; +} + +.cms-main-left-item-right-active { + background-color: #efefef !important; +} + +.cms-icon { + margin: 0; + padding: 0 35px 0 0; +} + + + + + + +/* +* CMS - MAIN - RIGHT +*/ +.cms-main-right-main { + margin: 0; + padding: 0 0 0 300px; + width: 100%; + height: auto; + background-color: #EFEFEF; +} + + + +.cms-main-right-main-navbar { + margin: 0; + padding: 0; + width: 100%; + height: 75px; + position: fixed; + left: 0; + background-color: white; + display: flex; + flex-direction: row; + justify-content: space-between; + text-transform: uppercase; + font-size: 14px; +} + +.cms-main-right-main-navbar-left { + margin: 0; + padding: 0 0 0 350px; + height: 75px; + display: flex; + align-items: center; + flex-direction: row; +} + +.cms-main-right-main-navbar-left-headline { + margin: 0; + padding: 0 50px 0 0; +} + +.cms-main-right-main-navbar-left-headline a { + margin: 0; + padding: 0; + color: #6C757D; + text-decoration: none; + border-bottom: 1px #6C757D dotted; +} + +.cms-main-right-main-navbar-right { + margin: 0; + padding: 0 50px 0 0; + display: flex; + align-items: center; +} + + +.cms-main-right-main-body { + margin: 0; + padding: 75px 50px 100px 50px; + width: auto; + height: auto; + overflow-y: auto !important; + display: flex; + justify-content: center; +} + +.cms-main-right-main-body-top { + margin: 0; + padding: 0 0 30px 0; + width: auto; + height: auto; + display: flex; + justify-content: space-between; +} + +.cms-main-right-main-body-top-left { + margin: 0; + padding: 0; + font-size: 28px; +} + +.cms-main-right-main-body-top-right { + margin: 0; + padding: 0; + display: flex; + align-items: center; +} + +.container { + margin: 50px 0 0 0; + padding: 50px; + border-radius: 25px; + background-color: white; + +} + +.text-start { + margin: 0; + padding: 10px 30px 10px 0; + +} + +.text-start a { + color: #6C757D; + text-decoration: none; + border-bottom: 1px #6C757D dotted; +} + +tbody { + font-size: 14px; +} + + + + + + + +/* +DASHBOARD +*/ + +.cms-dashboard { + margin: 0; + padding: 0; + display: flex; + flex-direction: column; +} + + + +.cms-dashboard-head { + margin-left: 10px; + padding: 0; + width: auto; + height: 65px; + font-size: 14px; + text-transform: uppercase; + display: flex; + align-items: center; +} + +.cms-dashboard-top { + margin: 0; + padding: 0; + display: flex; + border-radius: 25px; + flex-direction: row; + justify-content: space-between; + font-size: 14px; +} + +.cms-dashboard-top-item { + margin:0; + padding: 30px; + width: 300px; + height: auto; + border-radius: 25px; + background-color: white; +} + +.cms-dashboard-top-item img { + margin-left: auto; + margin-right: auto; + padding: 0; + display: block; + width: 75%; +} + +.cms-dashboard-top-item-title { + margin: 0 0 15px 0; + padding: 0 0 6px 0; + text-transform: uppercase; + border-style: solid; + border-width: 0 0 1px 0; + border-color: #ADB5BD; +} + +.cms-dashboard-bottom { + margin: 20px 0 0 0; + padding: 30px; + width: auto; + height: auto; + border-radius: 25px; + background-color: white; +} + +.cms-dashboard-bottom-title { + margin: 0; + padding: 0 0 20px 0; + text-transform: uppercase; +} + +.cms-dashboard-bottom th { + font-size: 14px; + font-weight: normal !important; +} + + + + + + + + +/* +* +* +* +* +* +* CREATE FILM SERIES +* +* +* +* +* +*/ + +/* CMS - Bottom Container - Parent */ +.cms-bottom-container-parent { + margin: 0; + padding: 0; + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +/* CMS - Bottom Container - Child */ +.cms-bottom-container-child { + margin: 0 0 25px 0; + padding: 50px; + width: 100%; + height: auto; + border-radius: 25px; + font-size: 15px; + background-color: white; +} + +/* CMS - Bottom Container - Child - Section - Heading */ +.cms-bottom-container-child-section-heading { + margin: 0; + padding-bottom: 10px; + font-size: 14px; + text-transform: uppercase; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +/* CMS - Bottom Container - Child - Section - Heading - Right */ +.cms-bottom-container-child-section-heading-right { + margin: 0; + padding-right: 50px; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +/* CMS - Bottom Container - Child - Section - Body */ +.cms-bottom-container-child-section-body { + margin: 0; + padding: 0 0 20px 0; + border-style: solid; + border-width: 1px 0 0 0; + border-color: #ADB5BD; +} + +.film { + border-style: solid; + border-width: 0 0 1px 0; + border-color: #ADB5BD; +} + +/* CMS - Bottom Container - Child - Section - Body - Series Status */ +.cms-bottom-container-child-section-body-status { + padding-left: 100px; + display: flex; + width: 500px; + justify-content: left; + align-items: center; +} + +/* CMS - Bottom Container - Child - Section - Body - Labels */ +label { + margin-bottom: 2px; + padding: 0; + font-size: 11px; + color: black; + text-transform: uppercase; + letter-spacing: .05em; + cursor: auto; +} + +/* CMS - Bottom Container - Child - Section - Body - Input and Textarea Fields */ +input, +textarea { + margin: 0; + border: 0; + padding: 5px; + border: 0.5px solid black; + color: black; + background-color: #efefef; + font-size: 14px; +} + +/* CMS - Bottom Container - Child - Section - Body - Input and Textarea Fields */ +/* WHEN DISABLED - WHEN VIEW ONLY */ +input:disabled, +textarea:disabled { + margin: 2px 0 0 0; + padding: 5px; + border: none; + background-color: #efefef; + resize: none; + cursor: text; +} + +/* CMS - Bottom Container - Child - Section - Body - Index */ +.cms-bottom-container-child-section-body-index { + padding: 5px; + font-size: 20px; + font-weight: bold; + border-radius: 50%; + border: solid 0.5px black; + width: 40px; + height: 40px; + background-color: #efefef !important; + color: black !important; + display: flex; + justify-content: center; + align-items: center; +} + +.cms-bottom-container-child-section-body-index.disabled { + border: solid 0.5px #EFEFEF; +} + +/* CMS - Bottom Container - Child - Section - Body - Index Placeholder Value */ +/* DO NOT DELETE: FOR CREATE PAGE */ +input.cms-bottom-container-child-section-body-index::-webkit-input-placeholder { + color: black !important; + text-align: center; +} + +/* CMS - Bottom Container - Child - Section - Body - Mini-site Parent */ +.cms-bottom-container-child-section-body-minisite-parent { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + font-family: Verdana, Arial, Helvetica, sans-serif; +} + +/* CMS - Bottom Container - Child - Section - Body - Mini-site Child */ +.cms-bottom-container-child-section-body-minisite-child { + display: flex; + flex-direction: column; +} + +.cms-bottom-container-child-section-body-minisite-child-top { + margin: 0; + padding: 10px; + width: auto; + height: 190px; + font-size: 10px; + text-transform: uppercase; + font-weight: bold; +} + +.cms-minisite-middle { + margin: 0; + padding: 0; + width: auto; + height: 120px; + display: flex; + flex-direction: row; +} + +.cms-minisite-middle-left { + margin: 0; + padding: 0; + width: 50px; + height: auto; +} + +.cms-bottom-container-child-section-body-minisite-child-right { + margin: 0; + padding-left: 15px; + width: 702px; + height: 120px; + font-size: 18px; + text-transform: uppercase; + font-weight: bold; + display: flex; + align-items: center; +} + + + + + +/* +* CMS - CONTENT +*/ + +.cms-content { + margin: 0; + padding: 0; + width: auto; + height: auto; + display: flex; + flex-direction: row; +} + +.cms-content a { + color: #6C757D; + text-decoration: none; + border-bottom: 1px #6C757D dotted; +} + +.cms-content-left { + margin: 0; + padding: 0; +} + + + +/* +* DASHBOARD +* SERIES +* FILMS +* ORG +*/ +.cms-content-middle { + margin: 0; + padding: 0; + width: 1130px; + height: auto; +} + +.cms-content-middle-head { + margin-left: 10px; + padding: 0; + width: auto; + height: 65px; + font-size: 14px; + text-transform: uppercase; + display: flex; + align-items: center; + flex-direction: row; + justify-content: space-between; +} + +.cms-content-middle-head-left { + margin-left: 0; + padding: 0; +} + +.cms-content-middle-head-right { + margin-left: 0; + padding: 0; +} + +.cms-content-middle-body { + margin-left: 10px; + padding: 0; + width: auto; + height: 65px; + font-size: 14px; + text-transform: uppercase; + display: flex; + align-items: center; +} + +.cms-content-middle-body-general { + margin: 0 0 20px 0; + padding: 30px; + border-radius: 25px; + background-color: white; +} + +.cms-content-middle-body-general table { + border-collapse: collapse; + width: 100%; +} + +.cms-content-middle-body-general th { + font-size: 14px; + font-weight: normal !important; + text-align: left; +} + + + + + + + + + +.cms-content-right { + margin: 0; + padding: 0; +} + + + + + + +/* +* +* CMS BUTTONS +* +*/ + +/* Use inline styles margin/padding to position button */ + +.cms-btn { + margin: 0; + padding: 0; + border-radius: 6px; + border: solid 1px #efefef; + font-size: 12px; + background-color: white; + color: #6C757D; + white-space: nowrap; + cursor: pointer; +} + +/* Button and font size only only */ +.cms-btn-large { + padding: 12px 22px 12px 22px !important; + font-size: 15px; +} +.cms-btn-medium { + padding: 5px 10px 5px 10px !important; +} +.cms-btn-small { + padding: 5px 7px 5px 7px !important; + min-width: 100px; +} + +/* Background/text color only */ +.cms-btn-create { + background-color: #343A40; + color: white; +} +.cms-btn-read { + margin: 0; + padding: 0; +} +.cms-btn-update { + background-color: #EFEFEF !important; +} +.cms-btn-delete { + padding: 5px 7px 5px 7px !important; +} + +.cms-btn:disabled { + margin: 0; + padding: 0; + border: solid 1px white; + background-color: #EFEFEF; + color: #6C757D; + cursor: not-allowed; +} + +.cms-btn-danger { + margin: 0; + padding: 5px 10px 5px 10px !important; + border-radius: 6px; + border: solid 1px red; + font-size: 14px; + background-color: white; + color: red; + white-space: nowrap; + cursor: pointer; +} + +.cms-btn-danger:disabled { + border: solid 1px #EFEFEF; + color: #6C757D; + cursor: not-allowed; +} + + + + +.cms-danger-item { + padding-bottom: 25px; + border-width: 0 0 0.5px 0; + border-style: solid; + border-color: black; + display: flex; + flex-direction: row; + justify-content: space-between; +} + + + + + + + + + +/* TO DELETE */ +.cms-content-middle th { + font-size: 14px; + font-weight: normal !important; +} + + + diff --git a/templates/apology.html b/templates/apology.html new file mode 100644 index 0000000..a414a39 --- /dev/null +++ b/templates/apology.html @@ -0,0 +1,10 @@ +{% extends "layout.html" %} + +{% block title %} + Apology +{% endblock %} + +{% block main %} + + {{ top }} +{% endblock %} diff --git a/templates/cms_create.html b/templates/cms_create.html new file mode 100644 index 0000000..79debf8 --- /dev/null +++ b/templates/cms_create.html @@ -0,0 +1,286 @@ +{% extends "cms_layout.html" %} + +{% block cmscontent %} + +{%- filter indent(width=8) -%} + +
+
+ LAFSCMS + / + Series + / + Create +
+
+
+
+ + + +
+ + +
+ + +
+ Create New Series +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+ +
+
+ + + +
+ + +
+ Create Schedule +
+ +
+ +
+
+ + + + + + + + +
+ + +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ + + + + +
+ +
+ + + +
+ + +
+ Create Website Theme +
+ + +
+ +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+ + +
+ FALL2023LANDSCAPE
+ ARCHITECTURE
FILMSERIES +
+ + +
+ +
+   +
+ +
+ I Am A Film Series  I Am A Film Series Subtitle +
+
+ + +
+   +
+ + +
+ +
+ +
+
+ + + +
+ +
+ +
+ + + + + +{%- endfilter -%} + +{% endblock %} \ No newline at end of file diff --git a/templates/cms_films.html b/templates/cms_films.html new file mode 100644 index 0000000..3b01a7a --- /dev/null +++ b/templates/cms_films.html @@ -0,0 +1,48 @@ +{% extends "cms_layout.html" %} + +{% block cmscontent %} + +
+
+ LAFSCMS + / + Films +
+
+
+
+ +
+ + + + + + + + + + + + + + + + {%- for film in films %} + + + + + + + + + + {% endfor %} + + +
SeriesTitleDirectorYearRuntimeIMDB IDWiki
{{ film.series }}{{ film.film_title }}{{ film.film_director }}{{ film.film_year }}{{ film.film_runtime }}{{ film.id }}wiki
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/cms_index.html b/templates/cms_index.html new file mode 100644 index 0000000..68df5f2 --- /dev/null +++ b/templates/cms_index.html @@ -0,0 +1,128 @@ +{% extends "cms_layout.html" %} + +{% block cmscontent %} + +
+ +
+
+ LAFSCMS + / + Dashboard +
+
+
+
+ +
+
+
+ Current Series +
+ +
+
+ +
+ Next Film +
+ + {{ next.film_title }}
+
+ (dir. {{ next.film_director }}, {{ next.film_year }}, {{ next.film_runtime }} mins) +
+ +




+ +
+ Special Note +
+ + {{ next.note }} + + + +
+
+
+ Quick Links +
+ + + Create New Series


+ + Update Current Series:
+ {{ current_series.series_title }}


+ + Edit Draft Series:
+ {% for series in serieses %} + {% set count = 0 %} + {% if series.series_display == "No" %} + {% set count = count + 1 %} + {{ series.series_title }}
+ {% else %} + {% endif %} + {% endfor %} + + {% if count == 0 %} + None + {% else %} + {% endif %} + +

+ + Register New User


+ +
+
+
+ +
+ + + + + + + + + + + + + + + +
+ Current Schedule +
+ + {%- for film in films %} + + + + + + + + + + {% endfor %} + + + + + + + +
DateTitleDirectorYearRuntimeIMDB IDWiki
{{ film.month[:3] }} {{ film.day }}{{ film.film_title }}{{ film.film_director }}{{ film.film_year }}{{ film.film_runtime }}{{ film.id }}wiki
+   +
+ +
+ +


















+ +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/cms_layout.html b/templates/cms_layout.html new file mode 100644 index 0000000..e6537da --- /dev/null +++ b/templates/cms_layout.html @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + {%- block map %}{% endblock %} + + + LAFSCMS + + + + + +
+ +
+ +
+ +
+
+ LAFSCMS +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ + arrow_back + + Website +
+
+
+ +
+
+
+
+
+ + logout + + Logout +
+
+
+ +
+ + +
+ +
+ +
+
+
+
+
+
+
+
+ Marty McFly + + account_circle + +
+
+ +
+ +
+
+
+
+ {%- block cmscontent %}{% endblock %} +
+
+
+
+ +
+
+ +
+ + + + + \ No newline at end of file diff --git a/templates/cms_media.html b/templates/cms_media.html new file mode 100644 index 0000000..8df8848 --- /dev/null +++ b/templates/cms_media.html @@ -0,0 +1,20 @@ +{% extends "cms_layout.html" %} + +{% block cmscontent %} + +
+
+ LAFSCMS + / + Media +
+
+
+
+ +
+ + +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/cms_org.html b/templates/cms_org.html new file mode 100644 index 0000000..32d2ba1 --- /dev/null +++ b/templates/cms_org.html @@ -0,0 +1,76 @@ +{% extends "cms_layout.html" %} + +{% block cmscontent %} + +
+
+ LAFSCMS + / + Org +
+
+
+
+ +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + {%- for user in users %} + + + + + + + + + + {% endfor %} + + +
NameRoleStatusUsernamePasswordCreatedUpdatedView/Edit
{{ user.name_first }} {{ user.name_last }}{{ user.role }} + {%- if user.status == 1 -%} + Active + {%- else -%} + — + {%- endif -%} + {{ user.username }}●●●●●●●●{{ user.date_created[:10] }} + {%- if user.date_updated -%} + {{ user.date_updated[:10] }} + {%- else -%} + — + {%- endif -%} + +
+ +
+
+ +
+ +
































+ +{% endblock %} \ No newline at end of file diff --git a/templates/cms_register.html b/templates/cms_register.html new file mode 100644 index 0000000..40f4a1e --- /dev/null +++ b/templates/cms_register.html @@ -0,0 +1,85 @@ +{% extends "cms_layout.html" %} + +{% block cmscontent %} + +{%- filter indent(width=8) -%} + +
+
+ LAFSCMS + / + Org + / + Register New User +
+
+
+
+ + + +
+ + +
+ + +
+ Register New User +
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ + + +
+ +
+ +
+ + + + + +{%- endfilter -%} + +{% endblock %} \ No newline at end of file diff --git a/templates/cms_series.html b/templates/cms_series.html new file mode 100644 index 0000000..2fdecaf --- /dev/null +++ b/templates/cms_series.html @@ -0,0 +1,98 @@ +{% extends "cms_layout.html" %} + +{% block cmscontent %} + +
+
+ LAFSCMS + / + Film Series +
+
+
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + {%- for series in serieses %} + + + + + + + + + + + + {% endfor %} + + +
SeriesSemesterTitlePosterStartEndStatusVisibleView/Edit
{{ series.series_id }}{{ series.series_semester[:2].upper() }} {{ series.series_year }}{{ series.series_title }} + {%- if series.series_poster_url -%} + view + {%- else -%} + — + {%- endif -%} + + {%- if series.min_date -%} + {{ series.min_date }} + {%- else -%} + — + {%- endif -%} + + {%- if series.max_date -%} + {{ series.max_date }} + {%- else -%} + — + {%- endif -%} + + {%- if series.status == 1 -%} + Completed + {%- elif series.status == 0 and series.series_display == "Yes" -%} + Ongoing + {%- else -%} + Draft + {%- endif -%} + {{ series.series_display }} +
+ {%- if series.status == 1 -%} + + {%- else -%} + + {%- endif %} +
+
+ +
+ +



























+



























+ +{% endblock %} \ No newline at end of file diff --git a/templates/cms_unpublish.html b/templates/cms_unpublish.html new file mode 100644 index 0000000..740a0b5 --- /dev/null +++ b/templates/cms_unpublish.html @@ -0,0 +1,22 @@ +{% extends "cms_layout.html" %} + +{% block cmscontent %} + +
+
+ LAFSCMS + / + Series + / + Unpublish +
+
+
+
+ +
+ + +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/cms_view.html b/templates/cms_view.html new file mode 100644 index 0000000..63fc676 --- /dev/null +++ b/templates/cms_view.html @@ -0,0 +1,484 @@ +{% extends "cms_layout.html" %} + +{% block cmscontent %} + +{%- filter indent(width=8) -%} + +
+
+ LAFSCMS + / + Film Series + / + {% if cms_status.status == 1 -%} + View Only + {%- else -%} + View or Edit + {%- endif -%} +
+
+
+
+ + + +
+ + +
+ + +
+
+ Film Series +
+
+
+ Status: + {% if cms_status.status == 1 -%} + Completed + {%- elif cms_status.status == 0 and cms_series.series_display == "Yes" -%} + Ongoing + {%- else -%} + Draft + {%- endif -%} +
+
+ Visible: {{ cms_series.series_display }} +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
+ + +
+ + +
+ Schedule +
+ +
+ + {% if cms_schedules|length > 0 %} + + {% for cms_schedule in cms_schedules %} +
+ + + + + +
+
+ {{- loop.index -}} +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+ +
+
+ {% endfor %} + + {%- else -%} +
+
+ + + + + + + + +
+ + +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+ +
+
+ {%- endif -%} + +
+ + + + + + + + + +
+ +
+ + + +
+ Website Theme +
+ +
+ +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ +
+
+ + +
+ {{- cms_series.series_semester -}}{{- cms_series.series_year -}}LANDSCAPE
ARCHITECTURE
FILMSERIES +
+ + +
+ +
+
+   +
+
+ +
+
+ {{ cms_series.series_title | safe }} + {{ cms_series.series_brief | safe }} POSTER + +
+
+
+ + +
+ +   + +
+ + + +
+
+ +
+ +
+ + +
+ +
+ +
+ + +



















+ +
+ + + +
+ Danger Zone +
+ +
+ +
+ +
+
+ Publish series
+ This will make the series visible on the website. +
+
+ {%- set danger_status = "" -%} + {%- if cms_series.series_display == "Yes" %} + {%- set danger_status = "disabled" %} + {%- endif %} + +
+
+ +
+ +
+
+ Unpublish series
+ This will remove the series from the website. Unpublished series can be deleted. +
+
+ {%- set danger_status = "" -%} + {%- if cms_series.series_display == "No" %} + {%- set danger_status = "disabled" %} + {%- endif %} +
+ + +
+
+
+ +
+ +
+
+ Delete series
+ This will delete an unpublished series and its schedules. It will also delete film records not + associated with any other series. There is no going back. Please be certain. +
+
+ {%- set danger_status = "" -%} + {%- if cms_series.series_display == "Yes" %} + {%- set danger_status = "disabled" %} + {%- endif %} +
+ +
+
+
+ +
+ +
+ + +{%- endfilter -%} + +{% endblock %} \ No newline at end of file diff --git a/templates/film.html b/templates/film.html new file mode 100644 index 0000000..682f0cf --- /dev/null +++ b/templates/film.html @@ -0,0 +1,40 @@ +{% extends "layout.html" %} + +{% block title %}{{ series.series_title }}{% endblock %} + +{% block main %} + +
+ + + {% for schedule in schedules %} + {% if schedule.id == film.id %} + {{ schedule.day }}.{{ schedule.month.lower() }}
+ {% endif %} + {% endfor %} + + + + {{ film.film_title.upper() }}
+
+ + + (dir. {{ film.film_director }}, {{ film.film_year }}, {{ film.film_runtime }}min)
+
+ + + {% if film.film_description %} + + {{ film.film_description }} +
+
+ {% endif %} + + + {% if film.wiki %} + wiki + {% endif %} + +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..5c9cda5 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,19 @@ +{% extends "layout.html" %} + +{% block title %}{{ series.semester }} — {{ series.series_title }}{% endblock %} + +{% block main %} + +
+ + {{ series.series_title.upper() }} + + {{ series.series_brief.upper() }} + {% if series.series_poster_url %} + POSTER + {% endif %} + + +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html new file mode 100644 index 0000000..cd46df5 --- /dev/null +++ b/templates/layout.html @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + {%- block map %}{% endblock %} + + + Landscape Architecture Film Series @ UIUC — {% block title %}{% endblock %} + + + + + +
+ + +
+ + +
+ {{ series.semester.upper() }}LANDSCAPE
ARCHITECTURE
FILMSERIES +
+ + + + + +
+ PLYM AUDITORIUM
+ 134 TEMPLE BUELL HALL
+ WEDNESDAYS @ 7:30pm
+ FREE ADMISSION
+
+ + +
+ {% for series_id in series_ids %} + {% if series_id.series_display == "Yes" %} +
+ + +
+ {% endif %} + {% endfor %} + + + +
+ +
+ + + + + + +
+ + +
+
+ + +
+ {% block main %}{% endblock %} +
+ +
+ + + + + + +
+ + + {% for schedule in schedules %} + +
+ + + + + +
+ + + + + + {{ schedule.day }}.{{ schedule.month.lower() }}
+
+ +
+ + + +
+ + + + + (dir. {{ schedule.film_director }}, + {{ schedule.film_year }}, + {{ schedule.film_runtime }}min)
+
+
+
+
+
+ + {% endfor %} + +
+ + +
+ +
+ +
+ +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/templates/map.html b/templates/map.html new file mode 100644 index 0000000..3795516 --- /dev/null +++ b/templates/map.html @@ -0,0 +1,19 @@ +{% extends "layout.html" %} + +{% block map %} + + +{% endblock %} + +{% block title %}{{ series.series_title }}{% endblock %} + +{% block main %} + + +
+ + + + +{% endblock %} \ No newline at end of file diff --git a/templates/org.html b/templates/org.html new file mode 100644 index 0000000..6152dea --- /dev/null +++ b/templates/org.html @@ -0,0 +1,25 @@ +{% extends "layout.html" %} + +{% block title %}{{ series.series_title }}{% endblock %} + +{% block main %} + +
+ + + ORG +
+
+ Susan Baumgartner
+ Gerard Bul-lalayao
+ David Hays
+ Chad Tyler
+
+ Technology and web space provided by the Digital + Design Rehearsal Studio.
+
+ +
+ +{% endblock %} \ No newline at end of file