def post(self): """ Responds to POST request by adding a new user item to the collection. If no errors happens while adding the user, the location of the new item is returned in the 'Location' response header. Otherwise an appropriate error code with a human-readable error message is returned. """ if not request.json: return create_error_response(415, 'Unsupported media type', 'Use JSON') try: validate(request.json, User.get_schema()) except ValidationError as e: return create_error_response(400, 'Invalid JSON document', str(e)) # Get arguments from the request username = request.json['username'].lower() # Lowercase the username email = request.json['email'] password = request.json['password'] # Check that username not taken if User.query.filter_by(username=username).count() > 0: return create_error_response( 409, 'Already exists', 'User with username "{}" already exists'.format(username)) # Check that email not taken if User.query.filter_by(email=email).count() > 0: return create_error_response( 409, 'Already exists', 'User with email "{}" already exists'.format(email)) # Create the new user entry user = User(username=username, email=email, password=password) # Attempt to add to database try: db.session.add(user) db.session.commit() except IntegrityError: db.session.rollback() return create_error_response( 409, 'Unexpected conflict', 'An unexpected conflict happened while committing to the database' ) # Respond to successful request return Response( status=201, headers={ 'Location': url_for('api.useritem', user=username) # Location of the added item })
def get(self, album, review): """ Responds to GET request with the representation of the requested review item (JSON document with added hypermedia controls (MASON)) If the requested review or the album for which the review should have been submitted does not exist in the API, 404 error code returned. : param str album: the unique name of the album for which the requested review have been submitted, provided in the request URL : param str review: the identifier of the requested review, provided in the request URL """ # Fetch the album and review items from the database and check if they exist album_item = Album.query.filter_by(unique_name=album).first() if not album_item: return create_error_response(404, 'Album not found') review_item = Review.query.filter(Review.identifier == review).filter( Review.album == album_item).first() if not review_item: return create_error_response(404, 'Review not found') # Create response user = review_item.user.username body = RevMusicBuilder(identifier=review, user=user, album=album_item.title, artist=album_item.artist, title=review_item.title, content=review_item.content, star_rating=review_item.star_rating, submission_date=datetime.datetime.strftime( review_item.submission_date, '%Y-%m-%d %H:%M:%S')) body.add_namespace('revmusic', LINK_RELATIONS_URL) body.add_control('self', url_for('api.reviewitem', album=album, review=review)) body.add_control('profile', REVIEW_PROFILE) body.add_control('author', url_for('api.useritem', user=user), title='The user who has submitted the review') body.add_control( 'about', url_for('api.albumitem', album=album), title='The album for which the review has been written') body.add_control_reviews_by(user) body.add_control_reviews_for(album) body.add_control_reviews_all() body.add_control_edit_review(album, review) body.add_control_delete_review(album, review) return Response(json.dumps(body), 200, mimetype=MASON)
def delete(self, album, review): """ Responds to DELETE request by deleting the requested review item. If the requested review or the album for which the review should have been submitted does not exist in the API, 404 error code returned. : param str album: the unique name of the album for which the requested review have been submitted, provided in the request URL : param str review: the identifier of the requested review, provided in the request URL """ # Fetch the album and review items from the database and check if they exist album_item = Album.query.filter_by(unique_name=album).first() if not album_item: return create_error_response(404, 'Album not found') review_item = Review.query.filter(Review.identifier == review).filter( Review.album == album_item).first() if not review_item: return create_error_response(404, 'Review not found') db.session.delete(review_item) db.session.commit() return Response(status=204)
def put(self, user): """ Responds to PUT request by replacing the user item's representation with the provided new one. If an error happens while handling the request, an appropriate error code with a human-readable error message is returned. : param str user: the username of the requested user, provided in the request URL """ if not request.json: return create_error_response(415, 'Unsupported media type', 'Use JSON') try: validate(request.json, User.get_schema()) except ValidationError as e: return create_error_response(400, 'Invalid JSON document', str(e)) # Fetch the requested user from the database and check that it exists db_user = User.query.filter_by(username=user).first() if not db_user: return create_error_response(404, 'User not found') username = request.json['username'].lower() # Lowercase the username email = request.json['email'] password = request.json['password'] # Check that possible new username is not taken if username != user: if User.query.filter_by(username=username).count() > 0: return create_error_response( 409, 'Already exists', 'User with username "{}" already exists'.format(username)) # Check that possible new email is not taken if email != db_user.email: if User.query.filter_by(email=email).count() > 0: return create_error_response( 409, 'Already exists', 'User with email "{}" already exists'.format(email)) # Updated user entry db_user.username = username db_user.email = email db_user.password = password # Commit changes try: db.session.commit() except IntegrityError: db.session.rollback() return create_error_response( 409, 'Unexpected conflict', 'An unexpected conflict happened while committing to the database' ) return Response( status=201, headers={ 'Location': url_for('api.useritem', user=username) # Location of the updated item })
def delete(self, user): """ Responds to DELETE request by deleting the requested user item. If the requested user does not exist in the API, 404 error code returned. : param str user: the username of the requested user, provided in the request URL """ # Fetch the requested user from the database and check that it exists db_user = User.query.filter_by(username=user).first() if not db_user: return create_error_response(404, 'User not found') db.session.delete(db_user) db.session.commit() return Response(status=204)
def delete(self, album): """ Responds to DELETE request by deleting the requested album item. If requested album does not exist in the API, 404 error code returned. : param str album: the unique name of the requested album, provided in the request URL """ # Fetch requested album item from database and check that it exists album_item = Album.query.filter_by(unique_name=album).first() if not album_item: return create_error_response(404, 'Album not found') db.session.delete(album_item) db.session.commit() return Response(status=204)
def get(self, album): """ Responds to GET request with a listing of all reviews for the specified album (JSON document with added hypermedia controls (MASON)) If the specified album does not exist in the API, 404 error code returned. : param str album: the unique name of the album the reviews of which are requested, provided in the request URL """ # Fetch the album item from the database and check whether it exists album_item = Album.query.filter_by(unique_name=album).first() if not album_item: return create_error_response(404, 'Album not found') body = RevMusicBuilder() body.add_namespace('revmusic', LINK_RELATIONS_URL) body.add_control('self', url_for('api.reviewsbyalbum', album=album)) body.add_control( 'up', url_for('api.albumitem', album=album), title='Album item for which the reviews have been submitted') body.add_control_reviews_all() body.add_control_add_review(album) # Fetch all the reviews from the database for the specified album reviews = Review.query.filter(Review.album == album_item).order_by( Review.submission_date.desc()).all() body['items'] = [] for review in reviews: item = RevMusicBuilder(identifier=review.identifier, user=review.user.username, title=review.title, star_rating=review.star_rating, submission_date=datetime.datetime.strftime( review.submission_date, '%Y-%m-%d %H:%M:%S')) item.add_control( 'self', url_for('api.reviewitem', album=album, review=review.identifier)) item.add_control('profile', REVIEW_PROFILE) body['items'].append(item) return Response(json.dumps(body), 200, mimetype=MASON)
def get(self, user): """ Responds to GET request with the representation of the requested user item (JSON document with added hypermedia controls (MASON)) If the requested user does not exist in the API, 404 error code returned. : param str user: the username of the requested user, provided in the request URL """ # Fetch the requested user from the database and check that it exists db_user = User.query.filter_by(username=user).first() if not db_user: return create_error_response(404, 'User not found') body = RevMusicBuilder(username=db_user.username, email=db_user.email) body.add_namespace('revmusic', LINK_RELATIONS_URL) body.add_control('self', url_for('api.useritem', user=db_user.username)) body.add_control('profile', USER_PROFILE) body.add_control_users_all('collection') body.add_control_reviews_by(user) body.add_control_edit_user(user) body.add_control_delete_user(user) return Response(json.dumps(body), 200, mimetype=MASON)
def get(self, album): """ Responds to GET request with the information of the requested album item (JSON document with added hypermedia controls (MASON)) If requested album does not exist in the API, 404 error code returned. : param str album: the unique name of the requested album, provided in the request URL """ # Fetch requested album item from database and check that it exists album_item = Album.query.filter_by(unique_name=album).first() if not album_item: return create_error_response(404, 'Album not found') # Handle optional data (date and time objects to string, if exists) release = album_item.publication_date duration = album_item.duration if release: release = release.strftime('%Y-%m-%d') if duration: duration = duration.strftime('%H:%M:%S') body = RevMusicBuilder(unique_name=album, title=album_item.title, artist=album_item.artist, release=release, duration=duration, genre=album_item.genre) body.add_namespace('revmusic', LINK_RELATIONS_URL) body.add_control('self', url_for('api.albumitem', album=album)) body.add_control('profile', ALBUM_PROFILE) body.add_control('collection', url_for('api.albumcollection'), title='All albums') body.add_control_reviews_for(album) body.add_control_edit_album(album) body.add_control_delete_album(album) return Response(json.dumps(body), 200, mimetype=MASON)
def post(self): """ Responds to POST request by adding a new album item to the collection. If no errors happens while adding the album, the location of the new item is returned in the 'Location' response header. Otherwise an appropriate error code with a human-readable error message is returned. """ if not request.json: return create_error_response(415, 'Unsupported media type', 'Use JSON') try: validate(request.json, Album.get_schema()) except ValidationError as e: return create_error_response(400, 'Invalid JSON document', str(e)) # Get arguments from request unique_name = request.json['unique_name'].lower() title = request.json['title'] artist = request.json['artist'] # Handle optional arguments release = None if 'release' in request.json: release = to_date(request.json['release']) if release is None: # None returned in case of an error return create_error_response( 400, 'Invalid release date', 'The release date you provided is an invalid date') duration = None if 'duration' in request.json: duration = to_time(request.json['duration']) # Schema forces the duration to be in the right form, so following check for error is not necessary #if duration is None:# None returned in case of an error # return create_error_response(400, 'Invalid duration', # 'The album duration you provided is an invalid time') genre = None if 'genre' in request.json: genre = request.json['genre'] # Check that unique_name is not in use if Album.query.filter_by(unique_name=unique_name).count() > 0: return create_error_response( 409, 'Already exists', 'Unique name "{}" is already in use'.format(unique_name)) # Create album entry and commit album = Album(unique_name=unique_name, title=title, artist=artist, publication_date=release, duration=duration, genre=genre) try: db.session.add(album) db.session.commit() except IntegrityError: db.session.rollback() return create_error_response( 409, 'Already exists', 'Album with title "{}" already exists with artist "{}"'.format( title, artist)) return Response( status=201, headers={ 'Location': url_for('api.albumitem', album=unique_name) # Location of the added item })
def put(self, album): """ Responds to PUT request by replacing the album item's representation with the provided new one. If an error happens while handling the request, an appropriate error code with a human-readable error message is returned. : param str album: the unique name of the requested album, provided in the request URL """ if not request.json: return create_error_response(415, 'Unsupported media type', 'Use JSON') try: validate(request.json, Album.get_schema()) except ValidationError as e: return create_error_response(400, 'Invalid JSON document', str(e)) # Fetch requested album item from database and check that it exists album_item = Album.query.filter_by(unique_name=album).first() if not album_item: return create_error_response(404, 'Album not found') # Get arguments from the request unique_name = request.json['unique_name'].lower() title = request.json['title'] artist = request.json['artist'] # Handle optional arguments; will be set to null if not provided release = None if 'release' in request.json: release = to_date(request.json['release']) if release is None: # None returned in case of an error return create_error_response( 400, 'Invalid release date', 'The release date you provided is an invalid date') duration = None if 'duration' in request.json: duration = to_time(request.json['duration']) # Schema forces the duration to be in the right form, so following check for error is not necessary #if duration is None: # None returned in case of an error # return create_error_response(400, 'Invalid duration', # 'The album duration you provided is an invalid time') genre = None if 'genre' in request.json: genre = request.json['genre'] # Check that unique_name is not in use in case that it is changed if unique_name != album and Album.query.filter_by( unique_name=unique_name).count() > 0: return create_error_response( 409, 'Already exists', 'Unique name "{}" is already in use'.format(unique_name)) # Update album values and commit album_item.unique_name = unique_name album_item.title = title album_item.artist = artist album_item.publication_date = release album_item.duration = duration album_item.genre = genre try: db.session.commit() except IntegrityError: db.session.rollback() return create_error_response( 409, 'Already exists', 'Album with title "{}" already exists with artist "{}"'.format( title, artist)) return Response( status=201, headers={ 'Location': url_for('api.albumitem', album=unique_name) # Location of the updated item })
def put(self, album, review): """ Responds to PUT request by replacing the review item's representation with the provided new one. If an error happens while handling the request, an appropriate error code with a human-readable error message is returned. : param str album: the unique name of the album for which the requested review have been submitted, provided in the request URL : param str review: the identifier of the requested review, provided in the request URL """ if not request.json: return create_error_response(415, 'Unsupported media type', 'Use JSON') try: validate(request.json, Review.get_schema()) except ValidationError as e: return create_error_response(400, 'Invalid JSON document', str(e)) # Fetch the album and review items from the database and check if they exist album_item = Album.query.filter_by(unique_name=album).first() if not album_item: return create_error_response(404, 'Album not found') review_item = Review.query.filter(Review.identifier == review).filter( Review.album == album_item).first() if not review_item: return create_error_response(404, 'Review not found') # Create an updated unique identifier and get the updated submission datetime for the review # Note: the probability of creating an identifier that already exist is practically zero, # but just to be absolutely sure the creation is tried until unique identifier is created. # Infinite looping extremely unlikely. while True: identifier, submission_dt = create_identifier('review_') if Review.query.filter_by(identifier=identifier).count() == 0: break original_user = review_item.user.username # Get the arguments from the request user = request.json['user'].lower() # Lowercase just in case title = request.json['title'] content = request.json['content'] star_rating = request.json['star_rating'] # Check whether provided user in the request matches the current writer of the review if user != original_user: return create_error_response( 409, 'Username does not match', 'Provided user "{}" has not submitted this review'.format( user)) # Update review values and commit review_item.identifier = identifier review_item.title = title review_item.content = content review_item.star_rating = star_rating review_item.submission_date = submission_dt try: db.session.commit() except IntegrityError: db.session.rollback() return create_error_response( 409, 'Unexpected conflict', 'An unexpected conflict happened while committing to the database' ) return Response( status=201, headers={ 'Location': url_for('api.reviewitem', album=album, review=identifier) # The location of the updated item })
def get(self): """ Responds to GET request with a listing of review items known to the API (JSON document with added hypermedia controls (MASON)) Query parameters in the request URL can be used to filter the returned reviews. """ body = RevMusicBuilder() body.add_namespace('revmusic', LINK_RELATIONS_URL) body.add_control_reviews_all('self') body.add_control_users_all() body.add_control_albums_all() # Obtain query parameters sent by user args = self.parse.parse_args() reviews = [] foreign_keys = [] timeframe = [] nlatest = args['nlatest'] # None or int if args['timeframe'] is not None: try: temp = args['timeframe'].split('_') if not temp[0][4:].isnumeric() or not temp[0][2:4].isnumeric( ) or not temp[0][0:2].isnumeric(): return create_error_response(415, 'Incorrect timeframe format') try: datetime.date(int(temp[0][4:]), int(temp[0][2:4]), int(temp[0][0:2])) except ValueError: return create_error_response(415, 'Incorrect timeframe format') timeframe.append('{}-{}-{}'.format(temp[0][4:], temp[0][2:4], temp[0][0:2])) # Check for possible second time if len(temp) is 2: if not temp[1][4:].isnumeric() or not temp[1][ 2:4].isnumeric() or not temp[1][0:2].isnumeric(): return create_error_response( 415, 'Incorrect timeframe format') try: datetime.date(int(temp[1][4:]), int(temp[1][2:4]), int(temp[1][0:2])) except ValueError: return create_error_response( 415, 'Incorrect timeframe format') timeframe.append('{}-{}-{}'.format(temp[1][4:], temp[1][2:4], temp[1][0:2])) # Make sure no more than 2 timeframes were provided if len(temp) > 2: raise Exception('More than two timeframe parameters') except Exception as e: # Return an error if an exception occurred return create_error_response( 415, 'Incorrect timeframe format', 'You provided an incorrect timeframe format. Please fix that >:( {}' .format(e)) # Error handling for nlatest is implemented by flask, since the type has been set to int # Handle filtering if not args['searchword']: # No searchword if len(timeframe) < 1: # No timeframe provided, return all or nlatest reviews = Review.query.order_by( Review.submission_date.desc()).limit(nlatest).all() elif len(timeframe) == 1: # One time provided, return all or nlatest after that reviews = Review.query.filter(func.date(Review.submission_date) >= timeframe[0])\ .order_by(Review.submission_date.desc()).limit(nlatest).all() else: # Two times provided, return all or nlatest between them reviews = Review.query.filter(func.date(Review.submission_date) >= timeframe[0])\ .filter(func.date(Review.submission_date) <= timeframe[1]).order_by(Review.submission_date.desc()).limit(nlatest).all() else: # Handle filtering if args['filterby'] == 'album': foreign_keys = Album.query.filter( Album.title.contains(args['searchword'])).all() elif args['filterby'] == 'artist': foreign_keys = Album.query.filter( Album.artist.contains(args['searchword'])).all() elif args['filterby'] == 'genre': foreign_keys = Album.query.filter( Album.genre.contains(args['searchword'])).all() else: # Filter by users foreign_keys = User.query.filter( User.username.contains(args['searchword'])).all() if len(timeframe) < 1: reviews = Review.query.filter( Review.album_id.in_([ fk.id for fk in foreign_keys ])).order_by( Review.submission_date.desc()).limit(nlatest).all() elif len(timeframe) == 1: reviews = Review.query.filter(Review.album_id.in_([fk.id for fk in foreign_keys])).filter(func.date(Review.submission_date) >= timeframe[0])\ .order_by(Review.submission_date.desc()).limit(nlatest).all() else: reviews = Review.query.filter(Review.album_id.in_([fk.id for fk in foreign_keys])).filter(func.date(Review.submission_date) >= timeframe[0])\ .filter(func.date(Review.submission_date) <= timeframe[1]).order_by(Review.submission_date.desc()).limit(nlatest).all() body['items'] = [] for review in reviews: item = RevMusicBuilder(identifier=review.identifier, user=review.user.username, album=review.album.title, title=review.title, star_rating=review.star_rating, submission_date=datetime.datetime.strftime( review.submission_date, '%Y-%m-%d %H:%M:%S')) item.add_control( 'self', url_for('api.reviewitem', album=review.album.unique_name, review=review.identifier)) item.add_control('profile', REVIEW_PROFILE) body['items'].append(item) return Response(json.dumps(body), 200, mimetype=MASON)
def post(self, album): """ Responds to POST request by adding a new review item to the specified album's review collection. If no errors happens while adding the review, the location of the new item is returned in the 'Location' response header. Otherwise an appropriate error code with a human-readable error message is returned. : param str album: the unique name of the album to which a new review is added, provided in the request URL """ if not request.json: return create_error_response(415, 'Unsupported media type', 'Use JSON') try: validate(request.json, Review.get_schema()) except ValidationError as e: return create_error_response(400, 'Invalid JSON document', str(e)) # Does the album for which the review is being submitted exist album_item = Album.query.filter_by(unique_name=album).first() if not album_item: return create_error_response(404, 'Album not found') # Create an unique identifier and get the submission datetime for the review # Note: the probability of creating an identifier that already exist is practically zero, # but just to be absolutely sure the creation is tried until unique identifier is created. # Infinite looping extremely unlikely. while True: identifier, submission_dt = create_identifier('review_') if Review.query.filter_by(identifier=identifier).count() == 0: break # Get the arguments from the request user = request.json['user'].lower() # Lowercase just in case title = request.json['title'] content = request.json['content'] star_rating = request.json['star_rating'] # Does the user by which the review is being submitted exist (provided in the request body) user_item = User.query.filter_by(username=user).first() if not user_item: return create_error_response(404, 'User not found') # Create a new review entry review = Review(identifier=identifier, user=user_item, album=album_item, title=title, content=content, star_rating=star_rating, submission_date=submission_dt) # Attempt to add to database try: db.session.add(review) db.session.commit() except IntegrityError: db.session.rollback() return create_error_response( 409, 'Already exists', 'User "{}" has already submitted a review to album with title "{}"' .format(user, album_item.title)) # Respond to successful request return Response( status=201, headers={ 'Location': url_for('api.reviewitem', album=album, review=identifier) # The location of the added item })