diff --git a/__pycache__/app.cpython-310.pyc b/__pycache__/app.cpython-310.pyc new file mode 100644 index 000000000..3e2c6ba83 Binary files /dev/null and b/__pycache__/app.cpython-310.pyc differ diff --git a/__pycache__/config.cpython-310.pyc b/__pycache__/config.cpython-310.pyc new file mode 100644 index 000000000..81d40701e Binary files /dev/null and b/__pycache__/config.cpython-310.pyc differ diff --git a/__pycache__/forms.cpython-310.pyc b/__pycache__/forms.cpython-310.pyc new file mode 100644 index 000000000..9068395da Binary files /dev/null and b/__pycache__/forms.cpython-310.pyc differ diff --git a/__pycache__/models.cpython-310.pyc b/__pycache__/models.cpython-310.pyc new file mode 100644 index 000000000..43e9734bb Binary files /dev/null and b/__pycache__/models.cpython-310.pyc differ diff --git a/app.py b/app.py index ed56020d7..43e8ff13c 100644 --- a/app.py +++ b/app.py @@ -5,12 +5,15 @@ import json import dateutil.parser import babel -from flask import Flask, render_template, request, Response, flash, redirect, url_for +from flask import Flask, render_template, request, Response, flash, redirect, url_for, jsonify from flask_moment import Moment from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import func + import logging from logging import Formatter, FileHandler from flask_wtf import Form +from flask_migrate import Migrate from forms import * #----------------------------------------------------------------------------# # App Config. @@ -21,52 +24,85 @@ app.config.from_object('config') db = SQLAlchemy(app) -# TODO: connect to a local postgresql database - #----------------------------------------------------------------------------# # Models. #----------------------------------------------------------------------------# +migrate = Migrate(app, db) + + + + class Venue(db.Model): - __tablename__ = 'Venue' + __tablename__ = 'venues' id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String) - city = db.Column(db.String(120)) - state = db.Column(db.String(120)) - address = db.Column(db.String(120)) - phone = db.Column(db.String(120)) + name = db.Column(db.String, nullable=False) + city = db.Column(db.String(120), nullable=False) + state = db.Column(db.String(120), nullable=False) + address = db.Column(db.String(120), nullable=False) + genres = db.Column(db.String(120), nullable=False) # Fixed typo 'geners' + phone = db.Column(db.String(120), nullable=False) image_link = db.Column(db.String(500)) facebook_link = db.Column(db.String(120)) + website_link = db.Column(db.String(120)) + lookingfortalent = db.Column(db.Boolean, nullable=False) + seek = db.Column(db.String(120), nullable=False) + + # Define many-to-many relationship with Artist through the Show model + shows = db.relationship('Show', back_populates='venue') + + # TODO: implement any missing fields, as a database migration using Flask-Migrate class Artist(db.Model): - __tablename__ = 'Artist' + __tablename__ = 'artists' id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String) + name = db.Column(db.String, nullable=False) city = db.Column(db.String(120)) state = db.Column(db.String(120)) phone = db.Column(db.String(120)) genres = db.Column(db.String(120)) image_link = db.Column(db.String(500)) facebook_link = db.Column(db.String(120)) + website_link = db.Column(db.String(120)) + lookingforVenue = db.Column(db.Boolean, nullable=False) + seek = db.Column(db.String(120), nullable=False) - # TODO: implement any missing fields, as a database migration using Flask-Migrate + # Define many-to-many relationship with Venue through the Show model + shows = db.relationship('Show', back_populates='artist') + +class Show(db.Model): + __tablename__ = 'shows' + + venue_id = db.Column(db.Integer, db.ForeignKey('venues.id'), primary_key=True) + artist_id = db.Column(db.Integer, db.ForeignKey('artists.id'), primary_key=True) + start_time = db.Column(db.DateTime, nullable=False) + + # Relationships + venue = db.relationship('Venue', back_populates='shows') + artist = db.relationship('Artist', back_populates='shows') -# TODO Implement Show and Artist models, and complete all model relationships and properties, as a database migration. #----------------------------------------------------------------------------# # Filters. #----------------------------------------------------------------------------# def format_datetime(value, format='medium'): - date = dateutil.parser.parse(value) + if isinstance(value, str): + date = dateutil.parser.parse(value) # Parse string into a datetime object + elif isinstance(value, datetime): + date = value # Use the datetime object as-is + else: + raise TypeError('Unsupported type for datetime formatting') + if format == 'full': - format="EEEE MMMM, d, y 'at' h:mma" + format = "EEEE MMMM, d, y 'at' h:mma" elif format == 'medium': - format="EE MM, dd, y h:mma" + format = "EE MM, dd, y h:mma" + return babel.dates.format_datetime(date, format, locale='en') app.jinja_env.filters['datetime'] = format_datetime @@ -85,129 +121,105 @@ def index(): @app.route('/venues') def venues(): - # TODO: replace with real venues data. - # num_upcoming_shows should be aggregated based on number of upcoming shows per venue. - data=[{ - "city": "San Francisco", - "state": "CA", - "venues": [{ - "id": 1, - "name": "The Musical Hop", - "num_upcoming_shows": 0, - }, { - "id": 3, - "name": "Park Square Live Music & Coffee", - "num_upcoming_shows": 1, - }] - }, { - "city": "New York", - "state": "NY", - "venues": [{ - "id": 2, - "name": "The Dueling Pianos Bar", - "num_upcoming_shows": 0, - }] - }] - return render_template('pages/venues.html', areas=data); + # Group venues by city and state + areas = ( + Venue.query + .with_entities(Venue.city, Venue.state) + .distinct() + .all() + ) + + # Structure the data with venues under each area + data = [] + for area in areas: + venues_in_area = Venue.query.filter_by(city=area.city, state=area.state).all() + data.append({ + "city": area.city, + "state": area.state, + "venues": [ + { + "id": venue.id, + "name": venue.name + } for venue in venues_in_area + ] + }) + + return render_template('pages/venues.html', areas=data) @app.route('/venues/search', methods=['POST']) def search_venues(): # TODO: implement search on artists with partial string search. Ensure it is case-insensitive. # seach for Hop should return "The Musical Hop". # search for "Music" should return "The Musical Hop" and "Park Square Live Music & Coffee" - response={ - "count": 1, - "data": [{ - "id": 2, - "name": "The Dueling Pianos Bar", - "num_upcoming_shows": 0, - }] + + search_term = request.form.get('search_term', '').strip() + result = Venue.query.filter( Venue.name.ilike(f"%{search_term}%")).all() + count = db.session.query(func.count(Venue.id)).filter(Venue.name.ilike(f"%{search_term}%")).scalar() + print(result) + # Prepare the response + response = { + "count": count, + "data": [] } + + # Iterate through the results and prepare the response data + for venue in result: + response["data"].append({ + "id":venue.id, + "name": venue.name, + }) + return render_template('pages/search_venues.html', results=response, search_term=request.form.get('search_term', '')) + @app.route('/venues/') def show_venue(venue_id): - # shows the venue page with the given venue_id - # TODO: replace with real venue data from the venues table, using venue_id - data1={ - "id": 1, - "name": "The Musical Hop", - "genres": ["Jazz", "Reggae", "Swing", "Classical", "Folk"], - "address": "1015 Folsom Street", - "city": "San Francisco", - "state": "CA", - "phone": "123-123-1234", - "website": "https://www.themusicalhop.com", - "facebook_link": "https://www.facebook.com/TheMusicalHop", - "seeking_talent": True, - "seeking_description": "We are on the lookout for a local artist to play every two weeks. Please call us.", - "image_link": "https://images.unsplash.com/photo-1543900694-133f37abaaa5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", - "past_shows": [{ - "artist_id": 4, - "artist_name": "Guns N Petals", - "artist_image_link": "https://images.unsplash.com/photo-1549213783-8284d0336c4f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80", - "start_time": "2019-05-21T21:30:00.000Z" - }], - "upcoming_shows": [], - "past_shows_count": 1, - "upcoming_shows_count": 0, - } - data2={ - "id": 2, - "name": "The Dueling Pianos Bar", - "genres": ["Classical", "R&B", "Hip-Hop"], - "address": "335 Delancey Street", - "city": "New York", - "state": "NY", - "phone": "914-003-1132", - "website": "https://www.theduelingpianos.com", - "facebook_link": "https://www.facebook.com/theduelingpianos", - "seeking_talent": False, - "image_link": "https://images.unsplash.com/photo-1497032205916-ac775f0649ae?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=750&q=80", - "past_shows": [], - "upcoming_shows": [], - "past_shows_count": 0, - "upcoming_shows_count": 0, - } - data3={ - "id": 3, - "name": "Park Square Live Music & Coffee", - "genres": ["Rock n Roll", "Jazz", "Classical", "Folk"], - "address": "34 Whiskey Moore Ave", - "city": "San Francisco", - "state": "CA", - "phone": "415-000-1234", - "website": "https://www.parksquarelivemusicandcoffee.com", - "facebook_link": "https://www.facebook.com/ParkSquareLiveMusicAndCoffee", - "seeking_talent": False, - "image_link": "https://images.unsplash.com/photo-1485686531765-ba63b07845a7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=747&q=80", - "past_shows": [{ - "artist_id": 5, - "artist_name": "Matt Quevedo", - "artist_image_link": "https://images.unsplash.com/photo-1495223153807-b916f75de8c5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=334&q=80", - "start_time": "2019-06-15T23:00:00.000Z" - }], - "upcoming_shows": [{ - "artist_id": 6, - "artist_name": "The Wild Sax Band", - "artist_image_link": "https://images.unsplash.com/photo-1558369981-f9ca78462e61?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=794&q=80", - "start_time": "2035-04-01T20:00:00.000Z" - }, { - "artist_id": 6, - "artist_name": "The Wild Sax Band", - "artist_image_link": "https://images.unsplash.com/photo-1558369981-f9ca78462e61?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=794&q=80", - "start_time": "2035-04-08T20:00:00.000Z" - }, { - "artist_id": 6, - "artist_name": "The Wild Sax Band", - "artist_image_link": "https://images.unsplash.com/photo-1558369981-f9ca78462e61?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=794&q=80", - "start_time": "2035-04-15T20:00:00.000Z" - }], - "past_shows_count": 1, - "upcoming_shows_count": 1, - } - data = list(filter(lambda d: d['id'] == venue_id, [data1, data2, data3]))[0] - return render_template('pages/show_venue.html', venue=data) + venue = Venue.query.get(venue_id) # Get the venue by ID + + if not venue: # Handle the case where the venue doesn't exist + flash(f"Venue with ID {venue_id} not found.") + return redirect(url_for('venues')) # Redirect to venues list + + # Query the shows for the venue + shows = Show.query.filter_by(venue_id=venue_id).join(Artist).all() + + # Separate past and upcoming shows + past_shows = [] + upcoming_shows = [] + + for show in shows: + show_data = { + "artist_id": show.artist.id, + "artist_name": show.artist.name, + "artist_image_link": show.artist.image_link if show.artist.image_link else 'default_image_url.jpg', + "start_time": show.start_time.strftime("%Y-%m-%d %H:%M:%S") # Format datetime + } + if show.start_time < datetime.now(): + past_shows.append(show_data) + else: + upcoming_shows.append(show_data) + + # Construct the data dictionary + data = { + "id": venue.id, + "name": venue.name, + "city": venue.city, + "state": venue.state, + "address": venue.address, + "genres": venue.genres.split(',') if venue.genres else [], + "phone": venue.phone, + "website_link": venue.website_link, + "facebook_link": venue.facebook_link, + "lookingfortalent": venue.lookingfortalent, + "seek": venue.seek, + "image_link": venue.image_link if venue.image_link else 'default_image_url.jpg', + "past_shows": past_shows, + "upcoming_shows": upcoming_shows, + "past_shows_count": len(past_shows), + "upcoming_shows_count": len(upcoming_shows), + } + + return render_template('pages/show_venue.html', venue=data) # Create Venue # ---------------------------------------------------------------- @@ -220,187 +232,224 @@ def create_venue_form(): @app.route('/venues/create', methods=['POST']) def create_venue_submission(): # TODO: insert form data as a new Venue record in the db, instead - # TODO: modify data to be the data object returned from db insertion + new_venue = Venue( + name=request.form.get('name'), + city=request.form.get('city'), + state=request.form.get('state'), + address=request.form.get('address'), + phone=request.form.get('phone'), + genres=','.join(request.form.getlist('genres')), + image_link=request.form.get('image_link'), + facebook_link=request.form.get('facebook_link'), + website_link = request.form.get('website_link'), + lookingfortalent=bool(request.form.get('seeking_talent')), + seek=request.form.get('seeking_description') + ) + # Add and commit the new venue to the database + db.session.add(new_venue) + db.session.commit() # on successful db insert, flash success - flash('Venue ' + request.form['name'] + ' was successfully listed!') + flash('Venue ' + request.form['name'] + ' was successfully listed!') # TODO: on unsuccessful db insert, flash an error instead. - # e.g., flash('An error occurred. Venue ' + data.name + ' could not be listed.') - # see: http://flask.pocoo.org/docs/1.0/patterns/flashing/ - return render_template('pages/home.html') + flash('An error occurred. Venue ' + request.form['phone'] + ' could not be listed.') + # see: http://flask.pocoo.org/docs/1.0/patterns/flashing/ + return render_template('pages/home.html') @app.route('/venues/', methods=['DELETE']) def delete_venue(venue_id): - # TODO: Complete this endpoint for taking a venue_id, and using - # SQLAlchemy ORM to delete a record. Handle cases where the session commit could fail. - - # BONUS CHALLENGE: Implement a button to delete a Venue on a Venue Page, have it so that - # clicking that button delete it from the db then redirect the user to the homepage - return None - -# Artists + try: + # Query the venue by id + venue = Venue.query.get(venue_id) + + if not venue: + return jsonify({"success": False, "error": "Venue not found"}), 404 + + # Delete the venue + db.session.delete(venue) + db.session.commit() + + # Return success response + return jsonify({"success": True, "message": f"Venue {venue_id} deleted successfully!"}), 200 + except Exception as e: + db.session.rollback() + print(f"Error: {e}") + return jsonify({"success": False, "error": "An error occurred while trying to delete the venue"}), 500 + finally: + db.session.close() + +# Artists # ---------------------------------------------------------------- @app.route('/artists') def artists(): - # TODO: replace with real data returned from querying the database - data=[{ - "id": 4, - "name": "Guns N Petals", - }, { - "id": 5, - "name": "Matt Quevedo", - }, { - "id": 6, - "name": "The Wild Sax Band", - }] - return render_template('pages/artists.html', artists=data) + + return render_template('pages/artists.html', artists=Artist.query.order_by('id').all()) @app.route('/artists/search', methods=['POST']) def search_artists(): # TODO: implement search on artists with partial string search. Ensure it is case-insensitive. # seach for "A" should return "Guns N Petals", "Matt Quevado", and "The Wild Sax Band". # search for "band" should return "The Wild Sax Band". - response={ - "count": 1, - "data": [{ - "id": 4, - "name": "Guns N Petals", - "num_upcoming_shows": 0, - }] + search_term =request.form.get('search_term', '') + result = Artist.query.filter(Artist.name.ilike(f"%{search_term}%")).all() + count = db.session.query(func.count(Artist.id)).filter(Artist.name.ilike(f"%{search_term}%")).scalar() + + response = { + "count": count, + "data": [] } + + for artist in result: + print("Artist found:", artist.id, artist.name, artist.city) + response["data"].append({ + "name": artist.name, + }) return render_template('pages/search_artists.html', results=response, search_term=request.form.get('search_term', '')) @app.route('/artists/') def show_artist(artist_id): - # shows the artist page with the given artist_id - # TODO: replace with real artist data from the artist table, using artist_id - data1={ - "id": 4, - "name": "Guns N Petals", - "genres": ["Rock n Roll"], - "city": "San Francisco", - "state": "CA", - "phone": "326-123-5000", - "website": "https://www.gunsnpetalsband.com", - "facebook_link": "https://www.facebook.com/GunsNPetals", - "seeking_venue": True, - "seeking_description": "Looking for shows to perform at in the San Francisco Bay Area!", - "image_link": "https://images.unsplash.com/photo-1549213783-8284d0336c4f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80", - "past_shows": [{ - "venue_id": 1, - "venue_name": "The Musical Hop", - "venue_image_link": "https://images.unsplash.com/photo-1543900694-133f37abaaa5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60", - "start_time": "2019-05-21T21:30:00.000Z" - }], - "upcoming_shows": [], - "past_shows_count": 1, - "upcoming_shows_count": 0, - } - data2={ - "id": 5, - "name": "Matt Quevedo", - "genres": ["Jazz"], - "city": "New York", - "state": "NY", - "phone": "300-400-5000", - "facebook_link": "https://www.facebook.com/mattquevedo923251523", - "seeking_venue": False, - "image_link": "https://images.unsplash.com/photo-1495223153807-b916f75de8c5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=334&q=80", - "past_shows": [{ - "venue_id": 3, - "venue_name": "Park Square Live Music & Coffee", - "venue_image_link": "https://images.unsplash.com/photo-1485686531765-ba63b07845a7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=747&q=80", - "start_time": "2019-06-15T23:00:00.000Z" - }], - "upcoming_shows": [], - "past_shows_count": 1, - "upcoming_shows_count": 0, - } - data3={ - "id": 6, - "name": "The Wild Sax Band", - "genres": ["Jazz", "Classical"], - "city": "San Francisco", - "state": "CA", - "phone": "432-325-5432", - "seeking_venue": False, - "image_link": "https://images.unsplash.com/photo-1558369981-f9ca78462e61?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=794&q=80", - "past_shows": [], - "upcoming_shows": [{ - "venue_id": 3, - "venue_name": "Park Square Live Music & Coffee", - "venue_image_link": "https://images.unsplash.com/photo-1485686531765-ba63b07845a7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=747&q=80", - "start_time": "2035-04-01T20:00:00.000Z" - }, { - "venue_id": 3, - "venue_name": "Park Square Live Music & Coffee", - "venue_image_link": "https://images.unsplash.com/photo-1485686531765-ba63b07845a7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=747&q=80", - "start_time": "2035-04-08T20:00:00.000Z" - }, { - "venue_id": 3, - "venue_name": "Park Square Live Music & Coffee", - "venue_image_link": "https://images.unsplash.com/photo-1485686531765-ba63b07845a7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=747&q=80", - "start_time": "2035-04-15T20:00:00.000Z" - }], - "past_shows_count": 0, - "upcoming_shows_count": 3, - } - data = list(filter(lambda d: d['id'] == artist_id, [data1, data2, data3]))[0] - return render_template('pages/show_artist.html', artist=data) + # Query the artist + artist = Artist.query.get(artist_id) + if not artist: + return render_template('errors/404.html'), 404 + + # Query the shows for the artist + shows = Show.query.filter_by(artist_id=artist_id).join(Venue).all() + + # Separate past and upcoming shows + past_shows = [] + upcoming_shows = [] + + for show in shows: + show_data = { + "venue_id": show.venue.id, + "venue_name": show.venue.name, + "venue_image_link": show.venue.image_link, + "start_time": show.start_time.strftime("%Y-%m-%d %H:%M:%S") # Format datetime + } + if show.start_time < datetime.now(): + past_shows.append(show_data) + else: + upcoming_shows.append(show_data) + + # Add show counts and data to the artist dictionary + artist_data = { + "id": artist.id, + "name": artist.name, + "city": artist.city, + "state": artist.state, + "phone": artist.phone, + "genres": artist.genres.split(","), + "image_link": artist.image_link, + "facebook_link": artist.facebook_link, + "website_link": artist.website_link, + "lookingforVenue": artist.lookingforVenue, + "seek": artist.seek, + "past_shows": past_shows, + "upcoming_shows": upcoming_shows, + "past_shows_count": len(past_shows), + "upcoming_shows_count": len(upcoming_shows), + } + + return render_template('pages/show_artist.html', artist=artist_data) + + + # Update # ---------------------------------------------------------------- @app.route('/artists//edit', methods=['GET']) def edit_artist(artist_id): - form = ArtistForm() - artist={ - "id": 4, - "name": "Guns N Petals", - "genres": ["Rock n Roll"], - "city": "San Francisco", - "state": "CA", - "phone": "326-123-5000", - "website": "https://www.gunsnpetalsband.com", - "facebook_link": "https://www.facebook.com/GunsNPetals", - "seeking_venue": True, - "seeking_description": "Looking for shows to perform at in the San Francisco Bay Area!", - "image_link": "https://images.unsplash.com/photo-1549213783-8284d0336c4f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80" - } - # TODO: populate form with fields from artist with ID + # shows the artist page with the given artist_id + artist = Artist.query.get(artist_id) + form = ArtistForm(obj=artist) + return render_template('forms/edit_artist.html', form=form, artist=artist) + @app.route('/artists//edit', methods=['POST']) def edit_artist_submission(artist_id): - # TODO: take values from the form submitted, and update existing - # artist record with ID using the new attributes - - return redirect(url_for('show_artist', artist_id=artist_id)) + try: + # Retrieve the artist by ID + artist = Artist.query.get(artist_id) + + # If artist doesn't exist, handle gracefully + if not artist: + flash(f"Artist with ID {artist_id} not found.") + return redirect(url_for('index')) + + # Update artist fields with form data + artist.name = request.form['name'] + artist.genres = ','.join(request.form.getlist('genres')) + artist.city = request.form['city'] + artist.state = request.form['state'] + artist.phone = request.form['phone'] + artist.website_link = request.form['website_link'] + artist.facebook_link = request.form['facebook_link'] + artist.lookingforVenue = bool(request.form.get('seeking_venue')) + artist.seek = request.form['seeking_description'] + artist.image_link = request.form['image_link'] + + # Commit the changes to the database + db.session.commit() + flash(f"Artist {artist.name} was successfully updated!") + return redirect(url_for('show_artist', artist_id=artist_id)) + + except Exception as e: + # Rollback in case of an error + db.session.rollback() + print("Error occurred:", e) + flash(f"An error occurred. Artist {artist_id} could not be updated.") + return redirect(url_for('show_artist', artist_id=artist_id)) + finally: + db.session.close() @app.route('/venues//edit', methods=['GET']) def edit_venue(venue_id): - form = VenueForm() - venue={ - "id": 1, - "name": "The Musical Hop", - "genres": ["Jazz", "Reggae", "Swing", "Classical", "Folk"], - "address": "1015 Folsom Street", - "city": "San Francisco", - "state": "CA", - "phone": "123-123-1234", - "website": "https://www.themusicalhop.com", - "facebook_link": "https://www.facebook.com/TheMusicalHop", - "seeking_talent": True, - "seeking_description": "We are on the lookout for a local artist to play every two weeks. Please call us.", - "image_link": "https://images.unsplash.com/photo-1543900694-133f37abaaa5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&q=60" - } - # TODO: populate form with values from venue with ID + # shows the artist page with the given artist_id + venue = Venue.query.get(venue_id) + form = VenueForm(obj=venue) + return render_template('forms/edit_venue.html', form=form, venue=venue) @app.route('/venues//edit', methods=['POST']) def edit_venue_submission(venue_id): - # TODO: take values from the form submitted, and update existing - # venue record with ID using the new attributes - return redirect(url_for('show_venue', venue_id=venue_id)) + try: + # Retrieve the artist by ID + venue = Venue.query.get(venue_id) + + # If artist doesn't exist, handle gracefully + if not venue: + flash(f"venue with ID {venue_id} not found.") + return redirect(url_for('index')) + + # Update artist fields with form data + venue.name = request.form['name'] + venue.genres = ','.join(request.form.getlist('genres')) + venue.city = request.form['city'] + venue.state = request.form['state'] + venue.phone = request.form['phone'] + venue.address = request.form['address'] + venue.website_link = request.form['website_link'] + venue.facebook_link = request.form['facebook_link'] + venue.lookingfortalent = bool(request.form.get('seeking_talent')) + venue.seek = request.form['seeking_description'] + venue.image_link = request.form['image_link'] + + # Commit the changes to the database + db.session.commit() + flash(f"Venue {venue.name} was successfully updated!") + return redirect(url_for('show_venue', venue_id=venue_id)) + + except Exception as e: + # Rollback in case of an error + db.session.rollback() + print("Error occurred:", e) + flash(f"An error occurred. venue {venue_id} could not be updated.") + return redirect(url_for('show_venue', venue_id=venue_id)) + finally: + db.session.close() + return redirect(url_for('show_venue', venue_id=venue_id)) # Create Artist # ---------------------------------------------------------------- @@ -412,14 +461,27 @@ def create_artist_form(): @app.route('/artists/create', methods=['POST']) def create_artist_submission(): - # called upon submitting the new artist listing form - # TODO: insert form data as a new Venue record in the db, instead - # TODO: modify data to be the data object returned from db insertion - - # on successful db insert, flash success + new_artist = Artist( + name=request.form.get('name'), + city=request.form.get('city'), + state=request.form.get('state'), + phone=request.form.get('phone'), + genres=','.join(request.form.getlist('genres')), + image_link=request.form.get('image_link'), + facebook_link=request.form.get('facebook_link'), + website_link=request.form.get('website_link'), # Fixed usage of 'request.form.get' + lookingforVenue=bool(request.form.get('seeking_venues')), + seek=request.form.get('seeking_description') + ) + + + # Add and commit the new venue to the database + db.session.add(new_artist) + db.session.commit() +# on successful db insert, flash success flash('Artist ' + request.form['name'] + ' was successfully listed!') - # TODO: on unsuccessful db insert, flash an error instead. - # e.g., flash('An error occurred. Artist ' + data.name + ' could not be listed.') +# TODO: on unsuccessful db insert, flash an error instead. + flash('An error occurred. Artist ' + request.form['name'] + ' could not be listed.') return render_template('pages/home.html') @@ -428,45 +490,20 @@ def create_artist_submission(): @app.route('/shows') def shows(): - # displays list of shows at /shows - # TODO: replace with real venues data. - data=[{ - "venue_id": 1, - "venue_name": "The Musical Hop", - "artist_id": 4, - "artist_name": "Guns N Petals", - "artist_image_link": "https://images.unsplash.com/photo-1549213783-8284d0336c4f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80", - "start_time": "2019-05-21T21:30:00.000Z" - }, { - "venue_id": 3, - "venue_name": "Park Square Live Music & Coffee", - "artist_id": 5, - "artist_name": "Matt Quevedo", - "artist_image_link": "https://images.unsplash.com/photo-1495223153807-b916f75de8c5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=334&q=80", - "start_time": "2019-06-15T23:00:00.000Z" - }, { - "venue_id": 3, - "venue_name": "Park Square Live Music & Coffee", - "artist_id": 6, - "artist_name": "The Wild Sax Band", - "artist_image_link": "https://images.unsplash.com/photo-1558369981-f9ca78462e61?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=794&q=80", - "start_time": "2035-04-01T20:00:00.000Z" - }, { - "venue_id": 3, - "venue_name": "Park Square Live Music & Coffee", - "artist_id": 6, - "artist_name": "The Wild Sax Band", - "artist_image_link": "https://images.unsplash.com/photo-1558369981-f9ca78462e61?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=794&q=80", - "start_time": "2035-04-08T20:00:00.000Z" - }, { - "venue_id": 3, - "venue_name": "Park Square Live Music & Coffee", - "artist_id": 6, - "artist_name": "The Wild Sax Band", - "artist_image_link": "https://images.unsplash.com/photo-1558369981-f9ca78462e61?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=794&q=80", - "start_time": "2035-04-15T20:00:00.000Z" - }] - return render_template('pages/shows.html', shows=data) + shows_data = [] + shows = Show.query.all() + + for show in shows: + print(f"Artist: {show.artist}, Image Link: {show.artist.image_link}") + shows_data.append({ + "venue_id": show.venue.id, + "artist_id": show.artist.id, + "venue_name": show.venue.name, + "artist_name": show.artist.name, + "artist_image_link": show.artist.image_link, + "start_time": format_datetime(show.start_time, format='full') + }) + return render_template('pages/shows.html', shows=shows_data) @app.route('/shows/create') def create_shows(): @@ -478,11 +515,23 @@ def create_shows(): def create_show_submission(): # called to create new shows in the db, upon submitting new show listing form # TODO: insert form data as a new Show record in the db, instead + try: + show =Show( + venue_id=request.form.get('venue_id'), + artist_id=request.form.get('artist_id'), + start_time=request.form.get('start_time') + ) + + db.session.add(show) + db.session.commit() + # on successful db insert, flash success + flash('Show was successfully listed!') + except: + db.session.rollback() + flash('An error occurred. Show could not be listed.') + finally: + db.session.close() - # on successful db insert, flash success - flash('Show was successfully listed!') - # TODO: on unsuccessful db insert, flash an error instead. - # e.g., flash('An error occurred. Show could not be listed.') # see: http://flask.pocoo.org/docs/1.0/patterns/flashing/ return render_template('pages/home.html') diff --git a/config.py b/config.py index c91475f47..3c469bcc5 100644 --- a/config.py +++ b/config.py @@ -7,7 +7,5 @@ DEBUG = True # Connect to the database - - -# TODO IMPLEMENT DATABASE URL -SQLALCHEMY_DATABASE_URI = '' +SQLALCHEMY_DATABASE_URI = 'postgresql://postgres:123@localhost:5432/fyyur' +SQLALCHEMY_TRACK_MODIFICATIONS = False \ No newline at end of file diff --git a/migrations/README b/migrations/README new file mode 100644 index 000000000..0e0484415 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/migrations/__pycache__/env.cpython-310.pyc b/migrations/__pycache__/env.cpython-310.pyc new file mode 100644 index 000000000..2501ca8a5 Binary files /dev/null and b/migrations/__pycache__/env.cpython-310.pyc differ diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 000000000..ec9d45c26 --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 000000000..4c9709271 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 000000000..2c0156303 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/5142ac660c0a_.py b/migrations/versions/5142ac660c0a_.py new file mode 100644 index 000000000..c55f17bba --- /dev/null +++ b/migrations/versions/5142ac660c0a_.py @@ -0,0 +1,99 @@ +"""empty message + +Revision ID: 5142ac660c0a +Revises: 64382ea203bd +Create Date: 2025-01-27 21:24:15.904865 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5142ac660c0a' +down_revision = '64382ea203bd' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('artists', + sa.Column('id', sa.Integer(), primary_key=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('city', sa.String(length=120), nullable=True), + sa.Column('state', sa.String(length=120), nullable=True), + sa.Column('phone', sa.String(length=120), nullable=True), + sa.Column('genres', sa.String(length=120), nullable=True), + sa.Column('image_link', sa.String(length=500), nullable=True), + sa.Column('facebook_link', sa.String(length=120), nullable=True), + sa.Column('website_link', sa.String(length=120), nullable=True), + sa.Column('lookingforVenue', sa.Boolean(), nullable=False), + sa.Column('seek', sa.String(length=120), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('venues', + sa.Column('id', sa.Integer(), primary_key=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('city', sa.String(length=120), nullable=False), + sa.Column('state', sa.String(length=120), nullable=False), + sa.Column('address', sa.String(length=120), nullable=False), + sa.Column('geners', sa.String(length=120), nullable=False), + sa.Column('phone', sa.String(length=120), nullable=False), + sa.Column('image_link', sa.String(length=500), nullable=True), + sa.Column('facebook_link', sa.String(length=120), nullable=True), + sa.Column('website_link', sa.String(length=120), nullable=True), + sa.Column('lookingfortalent', sa.Boolean(), nullable=False), + sa.Column('seek', sa.String(length=120), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + + op.create_table( + 'shows', + sa.Column('venue_id', sa.Integer, sa.ForeignKey('venues.id'), primary_key=True), + sa.Column('artist_id', sa.Integer, sa.ForeignKey('artists.id'), primary_key=True), + sa.Column('start_time', sa.DateTime, nullable=False), + ) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('shows', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.create_foreign_key('shows_artist_id_fkey', 'Artist', ['artist_id'], ['id']) + batch_op.create_foreign_key('shows_venue_id_fkey', 'Venue', ['venue_id'], ['id']) + + op.create_table('Artist', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('city', sa.VARCHAR(length=120), autoincrement=False, nullable=True), + sa.Column('state', sa.VARCHAR(length=120), autoincrement=False, nullable=True), + sa.Column('phone', sa.VARCHAR(length=120), autoincrement=False, nullable=True), + sa.Column('genres', sa.VARCHAR(length=120), autoincrement=False, nullable=True), + sa.Column('image_link', sa.VARCHAR(length=500), autoincrement=False, nullable=True), + sa.Column('facebook_link', sa.VARCHAR(length=120), autoincrement=False, nullable=True), + sa.Column('website_link', sa.VARCHAR(length=120), autoincrement=False, nullable=True), + sa.Column('lookingforVenue', sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.Column('seek', sa.VARCHAR(length=120), autoincrement=False, nullable=False), + sa.PrimaryKeyConstraint('id', name='Artist_pkey') + ) + op.create_table('Venue', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('city', sa.VARCHAR(length=120), autoincrement=False, nullable=False), + sa.Column('state', sa.VARCHAR(length=120), autoincrement=False, nullable=False), + sa.Column('address', sa.VARCHAR(length=120), autoincrement=False, nullable=False), + sa.Column('geners', sa.VARCHAR(length=120), autoincrement=False, nullable=False), + sa.Column('phone', sa.VARCHAR(length=120), autoincrement=False, nullable=False), + sa.Column('image_link', sa.VARCHAR(length=500), autoincrement=False, nullable=True), + sa.Column('facebook_link', sa.VARCHAR(length=120), autoincrement=False, nullable=True), + sa.Column('lookingfortalent', sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.Column('seek', sa.VARCHAR(length=120), autoincrement=False, nullable=False), + sa.PrimaryKeyConstraint('id', name='Venue_pkey') + ) + op.drop_table('venues') + op.drop_table('artists') + # ### end Alembic commands ### diff --git a/migrations/versions/64382ea203bd_.py b/migrations/versions/64382ea203bd_.py new file mode 100644 index 000000000..7f3f68824 --- /dev/null +++ b/migrations/versions/64382ea203bd_.py @@ -0,0 +1,64 @@ +"""empty message + +Revision ID: 64382ea203bd +Revises: +Create Date: 2025-01-27 16:59:31.523866 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '64382ea203bd' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('Artist', + sa.Column('id', sa.Integer(), primary_key=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('city', sa.String(length=120), nullable=True), + sa.Column('state', sa.String(length=120), nullable=True), + sa.Column('phone', sa.String(length=120), nullable=True), + sa.Column('genres', sa.String(length=120), nullable=True), + sa.Column('image_link', sa.String(length=500), nullable=True), + sa.Column('facebook_link', sa.String(length=120), nullable=True), + sa.Column('website_link', sa.String(length=120), nullable=True), + sa.Column('lookingforVenue', sa.Boolean(), nullable=False), + sa.Column('seek', sa.String(length=120), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('Venue', + sa.Column('id', sa.Integer(), primary_key=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('city', sa.String(length=120), nullable=False), + sa.Column('state', sa.String(length=120), nullable=False), + sa.Column('address', sa.String(length=120), nullable=False), + sa.Column('geners', sa.String(length=120), nullable=False), + sa.Column('phone', sa.String(length=120), nullable=False), + sa.Column('image_link', sa.String(length=500), nullable=True), + sa.Column('facebook_link', sa.String(length=120), nullable=True), + sa.Column('lookingfortalent', sa.Boolean(), nullable=False), + sa.Column('seek', sa.String(length=120), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('shows', + sa.Column('venue_id', sa.Integer(), nullable=True, primary_key=True), + sa.Column('artist_id', sa.Integer(), nullable=True, primary_key=True), + sa.Column('start_time', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['artist_id'], ['Artist.id'], ), + sa.ForeignKeyConstraint(['venue_id'], ['Venue.id'], ) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('shows') + op.drop_table('Venue') + op.drop_table('Artist') + # ### end Alembic commands ### diff --git a/migrations/versions/__pycache__/5142ac660c0a_.cpython-310.pyc b/migrations/versions/__pycache__/5142ac660c0a_.cpython-310.pyc new file mode 100644 index 000000000..c6c2e22b9 Binary files /dev/null and b/migrations/versions/__pycache__/5142ac660c0a_.cpython-310.pyc differ diff --git a/migrations/versions/__pycache__/64382ea203bd_.cpython-310.pyc b/migrations/versions/__pycache__/64382ea203bd_.cpython-310.pyc new file mode 100644 index 000000000..182ad187e Binary files /dev/null and b/migrations/versions/__pycache__/64382ea203bd_.cpython-310.pyc differ diff --git a/migrations/versions/__pycache__/bfb82614952c_updated_models_with_artist_venue_and_.cpython-310.pyc b/migrations/versions/__pycache__/bfb82614952c_updated_models_with_artist_venue_and_.cpython-310.pyc new file mode 100644 index 000000000..4abc7a183 Binary files /dev/null and b/migrations/versions/__pycache__/bfb82614952c_updated_models_with_artist_venue_and_.cpython-310.pyc differ diff --git a/migrations/versions/bfb82614952c_updated_models_with_artist_venue_and_.py b/migrations/versions/bfb82614952c_updated_models_with_artist_venue_and_.py new file mode 100644 index 000000000..67d1c0fff --- /dev/null +++ b/migrations/versions/bfb82614952c_updated_models_with_artist_venue_and_.py @@ -0,0 +1,70 @@ +"""Updated models with Artist, Venue, and Show relationships + +Revision ID: bfb82614952c +Revises: 5142ac660c0a +Create Date: 2025-01-27 22:00:09.015884 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'bfb82614952c' +down_revision = '5142ac660c0a' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### Recreate tables ### + # Venues table + op.create_table( + 'venues', + sa.Column('id', sa.Integer(), primary_key=True, nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('city', sa.String(length=120), nullable=False), + sa.Column('state', sa.String(length=120), nullable=False), + sa.Column('address', sa.String(length=120), nullable=False), + sa.Column('genres', sa.String(length=120), nullable=False), + sa.Column('phone', sa.String(length=120), nullable=False), + sa.Column('image_link', sa.String(length=500)), + sa.Column('facebook_link', sa.String(length=120)), + sa.Column('website_link', sa.String(length=120)), + sa.Column('lookingfortalent', sa.Boolean(), nullable=False), + sa.Column('seek', sa.String(length=120), nullable=False), + ) + + # Artists table + op.create_table( + 'artists', + sa.Column('id', sa.Integer(), primary_key=True, nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('city', sa.String(length=120)), + sa.Column('state', sa.String(length=120)), + sa.Column('phone', sa.String(length=120)), + sa.Column('genres', sa.String(length=120)), + sa.Column('image_link', sa.String(length=500)), + sa.Column('facebook_link', sa.String(length=120)), + sa.Column('website_link', sa.String(length=120)), + sa.Column('lookingforVenue', sa.Boolean(), nullable=False), + sa.Column('seek', sa.String(length=120), nullable=False), + ) + + # Shows table (many-to-many relationship) + op.create_table( + 'shows', + sa.Column('venue_id', sa.Integer(), sa.ForeignKey('venues.id', ondelete='CASCADE'), primary_key=True), + sa.Column('artist_id', sa.Integer(), sa.ForeignKey('artists.id', ondelete='CASCADE'), primary_key=True), + sa.Column('start_time', sa.DateTime(), nullable=False), + ) + + # ### end Alembic commands ### + + + +def downgrade(): + # ## Drop all tables ### + op.drop_table('venues') + op.drop_table('artists') + op.drop_table('shows') diff --git a/templates/forms/edit_artist.html b/templates/forms/edit_artist.html index 02472fe5d..2db23f4b0 100644 --- a/templates/forms/edit_artist.html +++ b/templates/forms/edit_artist.html @@ -26,8 +26,14 @@

Edit artist {{ artist.name }}

Ctrl+Click to select multiple - {{ form.genres(class_ = 'form-control', placeholder='Genres, separated by commas', autofocus = true) }} -
+
{{ form.facebook_link(class_ = 'form-control', placeholder='http://', autofocus = true) }} @@ -45,13 +51,14 @@

Edit artist {{ artist.name }}

- {{ form.seeking_venue(placeholder='Venue', autofocus = true) }} -
+
- {{ form.seeking_description(class_ = 'form-control', autofocus = true) }} -
+ diff --git a/templates/forms/edit_venue.html b/templates/forms/edit_venue.html index caeb78d39..10c871c43 100644 --- a/templates/forms/edit_venue.html +++ b/templates/forms/edit_venue.html @@ -30,8 +30,14 @@

Edit venue {{ venue.name }} Ctrl+Click to select multiple - {{ form.genres(class_ = 'form-control', placeholder='Genres, separated by commas', autofocus = true) }} - + + + -
- - {{ form.seeking_description(class_ = 'form-control', autofocus = true) }} -
+
+ +
diff --git a/templates/pages/show.html b/templates/pages/show.html index 6aff3ba5b..1cb5bb8a9 100644 --- a/templates/pages/show.html +++ b/templates/pages/show.html @@ -1,16 +1,15 @@ {% extends 'layouts/main.html' %} -{% block title %}Show Search{% endblock %} +{% block title %}Shows{% endblock %} {% block content %} - - -

Show Search

- -
- -{# Todo implement show search frontend #} - -{% endblock %} \ No newline at end of file +

Upcoming Shows

+
    + {% for show in shows %} +
  • + Venue: {{ show.venue_name }}
    + Artist: {{ show.artist_name }}
    + Start Time: {{ show.start_time }}
    + Artist Image +
  • + {% endfor %} +
+{% endblock %} diff --git a/templates/pages/show_artist.html b/templates/pages/show_artist.html index 2c33e52df..59276011b 100644 --- a/templates/pages/show_artist.html +++ b/templates/pages/show_artist.html @@ -26,16 +26,15 @@

{% if artist.facebook_link %}{{ artist.facebook_link }}{% else %}No Facebook Link{% endif %}

- {% if artist.seeking_venue %} -
-

Currently seeking performance venues

+ {% if artist.lookingforVenue %}
+

Currently seeking performance venues

- {{ artist.seeking_description }} + {{ artist.seek}}
{% else %}

- Not currently seeking performance venues + Not currently seeking talent

{% endif %}
@@ -43,6 +42,7 @@

Venue Image +

{{ artist.upcoming_shows_count }} Upcoming {% if artist.upcoming_shows_count == 1 %}Show{% else %}Shows{% endif %}

diff --git a/templates/pages/show_venue.html b/templates/pages/show_venue.html index 39d562b06..a3c077381 100644 --- a/templates/pages/show_venue.html +++ b/templates/pages/show_venue.html @@ -24,16 +24,16 @@

{% if venue.phone %}{{ venue.phone }}{% else %}No Phone{% endif %}

- {% if venue.website %}{{ venue.website }}{% else %}No Website{% endif %} + {% if venue.website_link %}{{ venue.website }}{% else %}No Website{% endif %}

{% if venue.facebook_link %}{{ venue.facebook_link }}{% else %}No Facebook Link{% endif %}

- {% if venue.seeking_talent %} + {% if venue.lookingfortalent %}

Currently seeking talent

- {{ venue.seeking_description }} + {{ venue.seek }}
{% else %} @@ -45,34 +45,34 @@

Venue Image
-

-
-

{{ venue.upcoming_shows_count }} Upcoming {% if venue.upcoming_shows_count == 1 %}Show{% else %}Shows{% endif %}

-
- {%for show in venue.upcoming_shows %} -
-
- Show Artist Image -
{{ show.artist_name }}
-
{{ show.start_time|datetime('full') }}
-
-
- {% endfor %} -
+
+

{{ venue.upcoming_shows_count }} Upcoming {% if venue.upcoming_shows_count == 1 %}Show{% else %}Shows{% endif %}

+
+ {% for show in venue.upcoming_shows %} +
+
+ Show Artist Image +
{{ show.artist_name }}
+
{{ show.start_time }}
+
+
+ {% endfor %} +
+
-

{{ venue.past_shows_count }} Past {% if venue.past_shows_count == 1 %}Show{% else %}Shows{% endif %}

-
- {%for show in venue.past_shows %} -
-
- Show Artist Image -
{{ show.artist_name }}
-
{{ show.start_time|datetime('full') }}
-
-
- {% endfor %} -
+

{{ venue.past_shows_count }} Past {% if venue.past_shows_count == 1 %}Show{% else %}Shows{% endif %}

+
+ {% for show in venue.past_shows %} +
+
+ Show Artist Image +
{{ show.artist_name }}
+
{{ show.start_time }}
+
+
+ {% endfor %} +
diff --git a/templates/pages/shows.html b/templates/pages/shows.html index 812a1a7c3..a8f61dd11 100644 --- a/templates/pages/shows.html +++ b/templates/pages/shows.html @@ -5,7 +5,7 @@ {%for show in shows %}
- Artist Image + Artist Image

{{ show.start_time|datetime('full') }}

{{ show.artist_name }}

playing at