From ccf76fd9d1fef878f40acbf563ea3ff2f24a3f73 Mon Sep 17 00:00:00 2001 From: mohammed akif Date: Tue, 28 Jan 2025 02:11:55 +0300 Subject: [PATCH] finish fyyur project --- __pycache__/app.cpython-310.pyc | Bin 0 -> 11305 bytes __pycache__/config.cpython-310.pyc | Bin 0 -> 405 bytes __pycache__/forms.cpython-310.pyc | Bin 0 -> 2707 bytes __pycache__/models.cpython-310.pyc | Bin 0 -> 1345 bytes app.py | 695 ++++++++++-------- config.py | 6 +- migrations/README | 1 + migrations/__pycache__/env.cpython-310.pyc | Bin 0 -> 2826 bytes migrations/alembic.ini | 50 ++ migrations/env.py | 113 +++ migrations/script.py.mako | 24 + migrations/versions/5142ac660c0a_.py | 99 +++ migrations/versions/64382ea203bd_.py | 64 ++ .../__pycache__/5142ac660c0a_.cpython-310.pyc | Bin 0 -> 2830 bytes .../__pycache__/64382ea203bd_.cpython-310.pyc | Bin 0 -> 1754 bytes ...els_with_artist_venue_and_.cpython-310.pyc | Bin 0 -> 1808 bytes ...c_updated_models_with_artist_venue_and_.py | 70 ++ templates/forms/edit_artist.html | 19 +- templates/forms/edit_venue.html | 25 +- templates/pages/show.html | 27 +- templates/pages/show_artist.html | 10 +- templates/pages/show_venue.html | 58 +- templates/pages/shows.html | 2 +- 23 files changed, 872 insertions(+), 391 deletions(-) create mode 100644 __pycache__/app.cpython-310.pyc create mode 100644 __pycache__/config.cpython-310.pyc create mode 100644 __pycache__/forms.cpython-310.pyc create mode 100644 __pycache__/models.cpython-310.pyc create mode 100644 migrations/README create mode 100644 migrations/__pycache__/env.cpython-310.pyc create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/5142ac660c0a_.py create mode 100644 migrations/versions/64382ea203bd_.py create mode 100644 migrations/versions/__pycache__/5142ac660c0a_.cpython-310.pyc create mode 100644 migrations/versions/__pycache__/64382ea203bd_.cpython-310.pyc create mode 100644 migrations/versions/__pycache__/bfb82614952c_updated_models_with_artist_venue_and_.cpython-310.pyc create mode 100644 migrations/versions/bfb82614952c_updated_models_with_artist_venue_and_.py diff --git a/__pycache__/app.cpython-310.pyc b/__pycache__/app.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e2c6ba83af891eaa5cf1c67c6de5d51209bb272 GIT binary patch literal 11305 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 literal 0 HcmV?d00001 diff --git a/__pycache__/config.cpython-310.pyc b/__pycache__/config.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81d40701e0cd5f518f849941fc551eed285f352e GIT binary patch literal 405 zcmYk2!ES;;5QY~-L0WH3dTV<1fLgU4Ow)>jEm1TIrjeKsbQh#S*0Ol8@6o#+`$~K5 z$yey9t4$LpnJ@GIlRudZE)-|e9%daGauz7&4lD}>hg(MbqqN2(4H87&qh`7MFPeANS}Vc} zFIuv7^S)lImi_H^lXQz0l*fdMo5YJDUQyZ7J7e8;d-{|LYcE|=(VLUM0ftHJtq_%5 z*AFA)x{f&a2x2zPYI=L}Os}m`U+s5ZbaU!zs;#zFOLr$@gWjAi?6KPEx#mzax<*H} zjp4xh0!e|Lf5OkxqB)xol#rQ;(QE-Lm3l*Ad4*l5XkO|mCWa^q<3RCoqNGctyrTJR hfnz@mO6x6^9#$A`B6NH?AeRghkhv>f$OsvU`vo`BaPa^D literal 0 HcmV?d00001 diff --git a/__pycache__/forms.cpython-310.pyc b/__pycache__/forms.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9068395da4b8a1d324dc35bb9419ba357ec6e237 GIT binary patch literal 2707 zcmbuBTXWk)6vri5mgQUGHW_H4K)4kQxCB~CDW%l8HECiewVkvP0i$>~j%r=pBRQRP zcnhzc;f43YTRsF|M6W#Y6*|+FbF|td4}=*8&FJX&-__pES)El-E*BKM{+Rx?|KPl$ z{7K^ErK7loclZ+;rZCl2JjGH}V$D@O&C(LixEW8kbT4aVJ;O4*oR#xT%S`HYH}4g! zf>*SPUdbw{%58;Znens246YqytTO5xGf|s~9z&gH1=NK^kF#Q1sh0l287NgXHklpq z$niL`z7T*{&Bn!c1diWdaJb7*aF_QYAtr=qjNHf>x?B{N=dgQ!I^Vn0laEIzBagX1T#K zJa?eN+eo_qadeM1?TI;%>5BEiUZY zu7k@DAdGX2@7TDiX0;fb-L7wYyxWZn-L4m~kxN|ab{~#xSJVJS63S%8$mGc6$!KWe zY!oniAI6an6?lcp$vnH+yS}v^atOB?0pIGe>FFz1Ywf!$HQQ%3a#O7lFz%x7YdZnd zqFr9Q$G5kTz3|d-A4=GXfp>Tgjj3p=rs^-XjI@SosDDKIW4D_{$Z{<$Bl}Bx2WD5AqSgWtV zn^=?JGQ1_xf+>kDcss?}I=mxs72cKDf%j6Jn}_!$&cekM7gped6mQqz!xR^npqk>+ z9DF2k1wNM8gij>ap_bxu8!k!Qgiljksl#-Njd}P?Vh8A3ptUvU;j+XHxFT^8zL0na zu1ajcml8MOn#5W7O5z+`PjPh-zLvNQHzc;;n-m*!a8qI(zLmHHw9F0l^|?}H#mrmKKHTia02~bth*5h-;SI; z4);Z;*%^1lL)a85KkM3I==5wCEK!o14Mu(h`|zF6g?Y-P5cp0HwnP!8Tz3H9OEK~X z@IxY=2 ztCMgRdx8lvlVp;ln`Bu$Torho44oNpflQlBhs-(|%9F4`rbuQ$#wBBt@yPgOjz7H7r|0BfaG3v} zlQDZ9=i(yEALZaenuAN2V~f0el!M18Cu6H|UP*HC8PrKGX5*NHD{-NooS~G2A@TTY zI8WjB#N%&}yi4ZT?TN?lQQ48lI|?cF)vQ*;iJ#xWkAKj86zxC;%i|mpo!Vm0CYv;w zck-h^JJ33tzH#aJFGT!7&@ZIevvk}iI|pd2f7+?Bc|-i5+*I+6a12dJDXACKNwuO? I%9F#t0b%-PQvd(} literal 0 HcmV?d00001 diff --git a/__pycache__/models.cpython-310.pyc b/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43e9734bb61bae90a3eda183b3cd5a8367bc9167 GIT binary patch literal 1345 zcmaJ>&u<$=6rNeHe`I4jElnwuB;8OBwbXS|8mAB;Ahan6B~>BNswSvf?|ZQ~-rZSt z#!b8@q@Fkc7bFe{sX6j5&6NY?m&aDuidmO&hU`h{n0?>m2DJ*5v-=E6HnZ8|5@$*6 z8k3g}IVUz}4`>k6j_kPHEz|ijl%WDusKE?4Fbj2PKojP^bqF~FXW=C{2lH?qUWNw>}RXsJ|K3t@kPU&DOQL4khR|U-T2iiX$yL~ zd=HZm>XM(PEuL8f+-Y%L+00ZZRqlK&28p}lk1@+sfc1*Kg6C4)d@Hk6*p4;!Yo@N4t04G$O>KA772(% zm$vSFSXlD*TQ|J*&f4{S=B`Muy1QZ&<%`qToHoQa2_|M-oWfHb6Eh}wes+o#bTZ*~ zzIbz#WtgaF>?T6FMvt+iu%n$jTj1^v5Hml~)sOrr-1m7o0@gfu~~^$dX`%FVIUHPM@m;>LFUS=Y>=uy>1L@b#X?sl zWe!czd;nb`sd1>NKQhv&XSR+4OqGE->RKkEsOJa6Us&dv3(fTBd%?!u=Ok3_?Fifp zV0CqE-P`?S+w&9fZu!dhcFg@$6OX#aQv>wwV{fkz)lSE{_71H_5xz@*QcMnLnrDvP o') 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 0000000000000000000000000000000000000000..2501ca8a57e462f04d7432fba8e7382f62986a55 GIT binary patch literal 2826 zcmaJ@UymEN5huB;-PN9?lP|d-u90Np)^G&$1Q%_BA~1r+PFw`F53SFs5#fNvYRQvU zS?ww$b(cHno|Hc1A%EWb(gz91OTR!rKtBdw`_zvRqlq)am2~IWs04CJ?r=E#&2MJ5 zY&Jszzkh%G_d&Z!$bWEh`RBsq575+i&~d_PPI~xcIUTbeqd0bQXYBS|J9hKh*z0+e z2yf)~e8Gf23g#5%WKrj}CGCaWM*$rP5mCav$`fD$|)Gf7S579 zCI`eN+SRoqc6yD1DU@7Dj69Eg0um>r!}g6o&IU5kqT@>kRGAQt7KO^nLK(*UCL9PI zi(-%!!qgv~PQ-^&meRD|*IH)%sb0;!O5~qJHb9H*p8WCdyN4euA=Tl7QXHoI)~(;Y zvv=^9`+G^j_dZB;viG3mBF~D!-cc#{^ib?Q7X8D@AT;0*Zr+`o$^caEKvPHwMt4{n z8vD_%KZgtoTV(FQ;H;_19IzQNPC5IEE*$P~cgfZWoklBkYL(o4BeB7#wto-2(i~=7 z#FIp+lTz|0^u`&xaiil|6O1#JxoLwBxsY+9Vg=9?1LI3E$&*w_G}}0^dI3%~0%8-> zxfHzAS-LI_#6W0M~lEOV4#Tx zX3UukHKIVSg6Co699Zwy$3iC@T%kIy@rQ{@z^$gP6NyIgzI+8#m|!jSLi-yix|X;F z^2&oQq#^aFL=&Ddkix>oMGs-3$pQ>q(sQEO$e96c=Mcr*`N{#(vPEr4zG>Q#w*(qL zCfs#_+;e!#-2?I)qOPAn6yyIBXt>s?CHLT%htndu;8K(o(arMc2W-5v#}igJxtruU*k7*A?~2bw3K9BNC$h(jai;BQgA6rR zEJb;^vOQ6yGMnhAoa)I`uawGN@X@1=Birya+bd?Q-EEqAriD!MxSthV?RR|RRalyB zON%5c;&Os_l9;hgeS@^6hPl!gkXm9blR_ma%9)m&7V(ASI@BJ0iF!6_UU^n3Ub*%i zIK)Oz*l=k8(G$8Mz5Y~QhxKQ8h}LY9=)Yis3E~R^EW}jmU@ASQGfW5WlJeS=$lV#4 zQSRQh3_WMFikokrI~pnq_h*g{fVtj3*o=NhzXncfZ&3&KHtZ-|vA4EnZ_x(s-oDIT z;P&P%uN@%QpP7UFqs#Ir(+SSLd=b%$=Xm~60e%Kzs#Fw-%u1kQMJgl=@(u{Kru2sB zknhSgv+s~qg|IHF;^I=EthkaofaCgy0Ao9VrZ5*{CaUuA+>x)%AjGedb7$s^oP`TT z^Nj(4H7MJEV*qh&;UVx?x(_z3Q`@A2&R*Y#D-4rj0O1Q^t}s6) z5f>WF6}$_i^nr|++T?2>Z-R8pk(J79O(#(OAhVYfWr9q_Vm#5O#>>mWKuF_e#ZlRD zE9wSP9A{VYsa_ce@TqDU zetw{v?VaTtxEpi0jIetXI)DSu>DfrGc5kirU@nqf6x@6SNE@uXFTB~GKvMk*x?ro} zBNiSDnMWB^J@-fEct3<*NMC>2`kwjMzn8xS!OpJn(KV1Zd#%-=TU|vmcyDS)d7?h8 zzGVE>_fvHf>-csh8{_v^TqI-oq=Adyx6YPtfdY$xEwyG_VOhJBzYsX-WA`d{78Oh@ z)-tmVfX+Su literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c6c2e22b9f5777d290855cd89ba8dc27a302ac9e GIT binary patch literal 2830 zcmbtW&2QT_6qjUKlI4#$iJPY1-a2ec0T+&&q+QSrOVea6)}~vVwU`k=ko{Oki7a^} z)67M8+OENlyC%n8_K)ni8?QU{R$x61J?xQkVyCqa*-Yq}@5jf-_nycf73cG=hQB}l z{H^tRLeu_|%HYpHG1LYHhw!ovqZamus_&wYkOF`KzB*=WZ;_yY4HDRh!X; z5qH+=xk1nM+@Qy%v>W_QqOyz+TgSY)wy%TEjja?I+}Jk{G)RHDmr_U?k+edr9%3V& zmKpD1Rp1bHhoae`Xl^K)S7_lKzhgsfi$l?IjMc(eCyt_;9I)%5`!*N{`axb#@-Csp z$@A{7Fqn5AJ@x*&xXRQ}bg)K!5C6X!r%zCgLa!Rrsv2idnmF5mvj_4Wn|r8X{O6AF z^T+TDh-Z?jADDPrG&}_t*M#j;9x%>?5x_d`(rkwYZJ&0%ZPFEHPOs_FLhysBx>JUYT-FC=wxXeq9evHc~ z@lSnuapOCN*=*d8$wm{Z)wzYrqi^q3{0J&I!>`xY zJ8$Wy=;|0%;j7h-sOon`Ra;0^pnhcFi?>!e0YGn26syq zF3X-G!H?GH8Qj81a~5mjSAj2FJZ$pdA_XFv=G_kQ>LtZ4-Vu*@PbEWIONu_}IrKnH z6!po(?vR8Mv-D%hV)QH;tgp@(BJc0;IEavH+Q?kgma{D>94RtV$S)z9lOiugL5eXc zic*YAfuBekl}C@3P}H+YEM7;&o%oxQqo<{qmL{%>Ls`&M5>26)$tBD;-tc*I)AK`4 zXpjEJfW{rK+2^#09y|6bByS0NK{~o9#U&I-lWXfMU#&b8_T#08%XgNrbL+u_dn-$8 z^s*dxl}|;l@@h#IjXQl>q@PH$48*&UVzAFqWp_{%je=g#OUPO0-&$4)dykQzuCAViWv6nzb+naCKeKYY-sx9{%VYeCX#?hVL%2SleHbvYyw>JM<~)LYHg zexteDXnk1UZFlxR>g?_A?6>yrwcAnj6#cWdZRN1>VXv~PZK!uvzb$0CIK?qWC#_4H z*wXplC6071y_rQ^;$64~DIroah`&Gr#7iV3{->G$6Z5-SC4yXVKwe0`ufXu(aQC;=xUCZ8eSzE zH~vT5ZW%fm4w8y zSEccYQBZygl!H(a>PaEaGkTIevn@+ipCm&VFm_@vRXKx!$mCo?UCD7Hqv;dIzXdvh z1LX;T6IC*t3KBw4sW94s3w3oVFB9}bq8B4}CRBK;kH;C=c6fk(@)9npoR}yPRuOv3 zWh}D-&dc-QcQSGw8be5JM_e}(RT-Km9ZM6qBNF9zSw1PK3La7kDRAWqI2+GzVVS2n^*)NJGGS-b#Pu<;s}f*#J0VhF#<1j{9@Q>rOqdr(uUZN=mIFITVwi=f{taANi4d&uP8xXz={{=WnAw zs+#tfYLp)x8ehQ2AA%uTUn7Xl== z<9tJpBMwdX@m|Lr9u9Zf+Z&q?w%TFdLwU#D+SqId;r6x{dV$kr6sB_bz;xWU*WPM* z8?E+dZoA#_ypH!^-P_pO+1YfQm!L~)BB`g-gMO{dENf-PP{oy>>(J=J$Jb$xgm#7q z68iTB(S?477aB1LCY4j1VJ2V|z%a2+jSR8DR!EgNr>KOkT#D8%MeCQMS1zJw2DzG{ zt7^7|TAx9#E#nHL@g@RFu0y2x#;R{DWAarm>L}NK6MEiCJIr0uUd&z6T_7bL1ya&m zApcL*+FPiq6{>D!s@{PSaH8N_|lF$->*#9<`nFMk9VF02{T zWF*G2!UbG%X$6EZ%DFU0Gyz9ersFiB(gD^H_2Vcxie4cgsUHR*9j56~hRNCq9r8%z z5pp$#jwl%&rc4AdfsmL}dIaoQnsRw{8IWZxOb^-0rJc#~Bl4n}3tNv!-|Fu6y1RQ{ z!fNd_ft;s8X)%?SYR;%eI0ZQjkq)R~!WR*!t}<19WG&2c*%#S{=sb{SH;reLL|Ts% zK}VEHvnLp=T3V0NG^Rl!o&A*2Xp}rro2iPxGa&q3WA}hCGynY5?F^oC%J|@EN(UkF zyv^-a@9VExP=Q)7B4|BTRSH;Juo*2ermgSja4?BREU!z0V;HOo)Sz56{8?V({Jh5b zs>b<+)O?G8-(R23r8S!Z6``t*@O?1Y(9sRlKx?nx2DH?>hVb9`THbq(o&Q4LE4-?s zJMcD;{V)3%s4f&M1Hi0U-32EvRtr>Ia{aYuC@;I9_<&)EWH*($1*WOzhv5*GPV=K! zZf5R%1YxpD(vu|9^$40=9kru7=n7(=D3%pz`94WQ--jy|)5$Oj3ts+19K_+6PUh^k zBC=Ug@nv06`pf2!1xYyeAwS?xL^^~{X+pT4CYkhXo=cZzKZG-x#q@K?0*yDoV6A4^ N2xE-YYa6vs{{zuy;p6}S literal 0 HcmV?d00001 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