class person(StructuredNode): id = IntegerProperty(unique_index=True) # traverse outgoing IS_FROM relations, inflate to Country objects following_p = RelationshipTo('person', 'FOLLOW') followed_p = RelationshipFrom('person', 'FOLLOW')
class PersonNode(StructuredNode): name = StringProperty() mobile = StringProperty() apartment = RelationshipTo(ApartmentNode, 'LIVES_IN')
class Supplier(StructuredNode): name = StringProperty() delivery_cost = IntegerProperty() coffees = RelationshipTo('Coffee', 'COFFEE SUPPLIERS') # Space to check for escaping
class District(StructuredNode): name = StringProperty(unique_index=True) city = RelationshipTo(City, "DistrictCityRel")
class BuildingNode(StructuredNode): address = StringProperty(unique_index=True) neighborhood = RelationshipTo('NeighborhoodNode', 'PART_OF') apartments = RelationshipFrom('ApartmentNode', 'PART_OF')
class App(StructuredNode): app_id = UniqueIdProperty() name = StringProperty() tags = RelationshipTo(Tag, 'TAGGED', model=AppTagRel)
class Vehicle(StructuredNode): code = StringProperty(required=True, unique_index=True) color = StringProperty(default="000000") description = StringProperty() to = RelationshipTo('Station', 'TO', model=FrTo)
class Track(SmallTrack): ''' Represents a Track on Spotify ''' album = RelationshipTo('model.graph.spotify.album.Album', 'FROM') artists = RelationshipTo('model.graph.spotify.artist.Artist', 'BY') playlists = RelationshipTo('model.graph.spotify.playlist.Playlist', 'FEATURED IN') # billboard_data = RelationshipTo('model.graph.billboard.track.Track', 'RANKED AS') genius_data = RelationshipTo('model.graph.genius.track.Track', 'ON GENIUS AS') available_markets = ArrayProperty() disc_number = IntegerProperty() duration_ms = IntegerProperty() explicit = BooleanProperty() external_ids = JSONProperty() external_urls = ArrayProperty() href = StringProperty() is_local = BooleanProperty() preview_url = StringProperty() track_number = IntegerProperty() type = StringProperty() # We can worry about analysis at a later point analysis = JSONProperty() # Features key = IntegerProperty() mode = IntegerProperty() time_signature = IntegerProperty() acousticness = FloatProperty() danceability = FloatProperty() energy = FloatProperty() instrumentalness = FloatProperty() liveness = FloatProperty() loudness = FloatProperty() speechiness = FloatProperty() valence = FloatProperty() tempo = FloatProperty() analysis_url = StringProperty() popularity = IntegerProperty() @classmethod @db.transaction def add_genius(cls, uri, data): ''' Updates a node to add Genius data :param uri: the Spotify URI of the track :param data: the genius data :return: the updated node ''' track = cls.nodes.get_or_none(uri=uri) if track: track.genius_data = data track.save() return track @classmethod def add_zVector(cls, uri, data: List[List[float]]): ''' Adds zVector data to the track :param uri: the Spotify URI of the track :param data: the zVector data as a list of lists, not a numpy array :return: the updated node ''' # print(uri) db.set_connection(connection_url()) # query = 'MATCH (n: Track {uri: "%s"}) RETURN n LIMIT 1' % (uri) # results, meta = db.cypher_query(query=query) # track = [cls.inflate(row[0]) for row in results] # # if track: # track = track[0] # print(track.name) # print('Please save', track) # f**k neomodel ffs data = json.dumps({'zVector': data}) query = ''' MATCH (t:Track {uri:'%s'}) WITH t, properties(t) as snapshot SET t.zVector = '%s' RETURN snapshot ''' % (uri, data) # print(query) track = db.cypher_query(query) return track def get_song_features(self, as_list=False): ''' Gets the song features as a dictionary :param as_list: whether to return a list or dictionary :return: the song features ''' cols = [ 'key', 'mode', 'time_signature', 'acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'valence', 'tempo', 'popularity' ] tor = self.get_features(cols) for key, val in tor.items(): if val is None: tor[key] = 0 if as_list: return [tor[a] for a in cols] return tor @classmethod def get_songs_in_playlists(cls, limit, offset=0): ''' Gets only songs that appear in playlists :return: the query to do that ''' query = ''' MATCH (t: Track)-[r:`FEATURED IN`]->(p: Playlist) WITH t, count(r) as playlist_count WHERE playlist_count > 0 RETURN t ''' return cls.paginate(limit, offset, query) @classmethod def get_songs_not_in_playlists(cls, limit, offset=0): ''' Gets only songs that appear in playlists :return: the query to do that ''' query = ''' MATCH (t: Track) WHERE NOT EXISTS (t.zVector) RETURN t ''' return cls.paginate(limit, offset, query)
class Book(StructuredNode): title = StringProperty(unique_index=True) author = RelationshipTo('Author', 'AUTHOR')
class Function(StructuredNode): name = StringProperty(required=True) decompilation = StringProperty() imports_symbol = RelationshipTo('Symbol', 'imports') defined_at = RelationshipFrom('File', 'defines')
class Dog(StructuredNode): name = StringProperty(required=True) owner = RelationshipTo('Person', 'owner')
class File(StructuredNode): name = StringProperty(unique_index=True) path = StringProperty() hash = StringProperty() uses = RelationshipTo('Symbol', 'uses') provides = RelationshipTo('Symbol', 'provides')
class Pleb(Searchable): """ Signals and overwritting the save method don't seem to have any affect currently. We'll want to look into this and instead of having cache methods sprinkled around the code, overwrite get/save methods to first check the cache for the id of the object. Should also be updating/destroying the document in the search index upon save/update/destroy """ search_modifiers = { 'post': 10, 'comment_on': 5, 'upvote': 3, 'downvote': -3, 'time': -1, 'proximity_to_you': 10, 'proximity_to_interest': 10, 'share': 7, 'flag_as_inappropriate': -5, 'flag_as_spam': -100, 'flag_as_other': -10, 'solution': 50, 'starred': 150, 'seen_search': 5, 'seen_page': 20 } gender = StringProperty() oauth_token = StringProperty() username = StringProperty(unique_index=True) first_name = StringProperty() last_name = StringProperty() middle_name = StringProperty() # Just an index as some individuals share email addresses still email = StringProperty(index=True) date_of_birth = DateTimeProperty() primary_phone = StringProperty() secondary_phone = StringProperty() profile_pic = StringProperty(default=get_default_profile_pic) wallpaper_pic = StringProperty(default=get_default_wallpaper_pic) reputation = IntegerProperty(default=0) is_rep = BooleanProperty(default=False) is_admin = BooleanProperty(default=False) is_sage = BooleanProperty(default=False) is_verified = BooleanProperty(default=True) search_index = StringProperty() reputation_update_seen = BooleanProperty(default=True) # Placeholders that enable us to gather a user's employer and # occupation if they are donating to a political campaign mission # We'll eventually be transitioning these to a node of their own. employer_name = StringProperty() occupation_name = StringProperty() # base_index_id is the plebs id in the base elasticsearch index base_index_id = StringProperty() email_verified = BooleanProperty(default=False) populated_personal_index = BooleanProperty(default=False) initial_verification_email_sent = BooleanProperty(default=False) stripe_customer_id = StringProperty() # The credit card associated with the customer stripe_default_card_id = StringProperty() # last_counted_vote_node is the node we want to query on to get # reputation change over time last_counted_vote_node = StringProperty(default=None) # vote_from_last_refresh is what gets stored every time a user # refreshes their page, allows us to easily swap it with # last_counted_vote_node when they check their reputation vote_from_last_refresh = StringProperty(default=None) mission_signup = StringProperty(default=None) # Relationships privileges = RelationshipTo('sb_privileges.neo_models.Privilege', 'HAS', model=ActionActiveRel) actions = RelationshipTo('sb_privileges.neo_models.SBAction', 'CAN', model=ActionActiveRel) restrictions = RelationshipTo('sb_privileges.neo_models.Restriction', 'RESTRICTED_BY', model=RestrictionRel) badges = RelationshipTo("sb_badges.neo_models.Badge", "BADGES") oauth = RelationshipTo("plebs.neo_models.OauthUser", "OAUTH_CLIENT") tags = RelationshipTo('sb_tags.neo_models.Tag', 'TAGS', model=TagRelationship) voted_on = RelationshipTo('sb_base.neo_models.VotableContent', 'VOTES') viewed = RelationshipTo('sb_search.neo_models.Searchable', "VIEWED", model=Impression) interests = RelationshipTo("sb_tags.neo_models.Tag", "INTERESTED_IN") friends = RelationshipTo("Pleb", "FRIENDS_WITH", model=FriendRelationship) # Optimization wall = RelationshipTo('sb_wall.neo_models.Wall', 'OWNS_WALL') friend_requests_sent = RelationshipTo( "plebs.neo_models.FriendRequest", 'SENT_A_REQUEST') friend_requests_received = RelationshipTo( "plebs.neo_models.FriendRequest", 'RECEIVED_A_REQUEST') user_weight = RelationshipTo('Pleb', 'WEIGHTED_USER', model=UserWeightRelationship) object_weight = RelationshipTo( 'sb_base.neo_models.SBContent', 'OBJECT_WEIGHT', model=RelationshipWeight) searches = RelationshipTo('sb_search.neo_models.SearchQuery', 'SEARCHED', model=SearchCount) clicked_results = RelationshipTo('sb_search.neo_models.SearchResult', 'CLICKED_RESULT') official = RelationshipTo('sb_public_official.neo_models.PublicOfficial', 'IS_AUTHORIZED_AS', model=OfficialRelationship) # TODO our queries might not need HAS_SENATOR or HAS_HOUSE_REPRESENTATIVE # this distinction is covered by our endpoints of senators/house # representatives. We can change the relationship to REPRESENTED_BY # and utilize the Senate/House Representative/etc label to get the proper # official. This would require a Senator label though and not sure how we # want to handle this in a scalable way where we don't have to make new # relationships and classes for every new type of rep. Could potentially # define a few and then use a general class with an attribute that inherits # from them. Or we could start adding Labels that aren't associated with # classes based on the type of rep. This would allow us to use the same # types of queries and easily get all the types of representatives. senators = RelationshipTo('sb_public_official.neo_models.PublicOfficial', 'HAS_SENATOR') house_rep = RelationshipTo('sb_public_official.neo_models.PublicOfficial', 'HAS_HOUSE_REPRESENTATIVE') president = RelationshipTo('sb_public_official.neo_models.PublicOfficial', 'HAS_PRESIDENT') flags = RelationshipTo('sb_flags.neo_models.Flag', "FLAGS") beta_user = RelationshipTo('plebs.neo_models.BetaUser', "BETA_USER") url_content = RelationshipTo('sb_uploads.neo_models.URLContent', 'URL_CONTENT') address = RelationshipTo("sb_address.neo_models.Address", 'LIVES_AT') # Users can only have one campaign as the campaign is essentially their # action page and account information. They won't be able to create # multiple accounts or multiple action pages. We can then utilize the # campaign as another type of wall where they associate Projects or other # things to. If a user is waging a `PoliticalCampaign` their Action page # changes a little and they start being able to receive pledged votes and # there are more limitations on how donations occur. # Quest # Access if this Pleb is waging a Quest # Neomodel: quest Cypher IS_WAGING # RelationshipFrom('sb_quests.neo_models.Quest') # Edits # Access if this Pleb can edit a Quest through: # Neomodel: editors Cypher: EDITOR_OF # RelationshipTo('sb_quests.neo_models.Quest') # Moderator # Access if this Pleb can view financial data of a Quest through: # Neomodel: moderators Cypher: MODERATOR_OF # RelationshipTo('sb_quests.neo_models.Quest') # Volunteer # Access what this Pleb has volunteered to do through: # Neomodel: volunteer Cypher: WANTS_TO # RelationshipFrom('sb_volunteers.neo_models.Volunteer') # Use stripe_account on quest instead stripe_account = StringProperty() # Can this just be a vote? Have it set like we do with votable content # with the assumption we can # utilize a different serializer that only enables up/off votes rather than # also allowing down votes to be cast. Then we can utilize the different # relationship to track any special items. donations = RelationshipTo('sb_donations.neo_models.Donation', 'DONATIONS_GIVEN') following = RelationshipTo('plebs.neo_models.Pleb', 'FOLLOWING', model=FollowRelationship) party_affiliations = RelationshipTo('plebs.neo_models.PoliticalParty', 'AFFILIATES_WITH') activity_interests = RelationshipTo('plebs.neo_models.ActivityInterest', 'WILL_PARTICIPATE') @property def customer_token(self): # DO NOT USE: NON-USE PLACEHOLDER FOR SERIALIZER return None @classmethod def get(cls, username, cache_buster=False): profile = None if username is None: return None if cache_buster is False: profile = cache.get(username) if profile is None or cache_buster: res, _ = db.cypher_query( "MATCH (a:%s {username:'******'}) RETURN a" % ( cls.__name__, username)) if res.one: res.one.pull() profile = cls.inflate(res.one) else: raise DoesNotExist('Profile with username: %s ' 'does not exist' % username) cache.set(username, profile) return profile @classmethod def clear_unseen_friend_requests(cls, username): """ This method sets all the existing friend requests a given Pleb has to seen. The method doesn't return anything because if the query fails it will throw a Cypher Exception which will be caught and handled by a view. Please wrap this function with the proper handler if you are calling it in a task. Limitations: This method requires that a username be passed rather than being executed on an initialized Pleb. Made this into a class method rather than a method that could be used on an initialized Pleb for optimization. Instead of querying a Pleb then having to execute this same query utilizing self.username it is more efficient to utilize a defined username that we can retrieve from something like request.user.username without having to get the Pleb. If there is want to transition this to a non-classmethod we could utilize the cache to get the initial Pleb and then call it based on that but that still runs the chance of executing two queries. :param username: :return: """ value = get_current_time().astimezone(pytz.utc) epoch_date = datetime(1970, 1, 1, tzinfo=pytz.utc) time_seen = float((value - epoch_date).total_seconds()) query = 'MATCH (a:Pleb {username: "******"})-[:RECEIVED_A_REQUEST]->' \ '(n:FriendRequest) WHERE n.seen=False' \ ' SET n.seen = True, ' \ 'n.time_seen = %s' % (username, time_seen) db.cypher_query(query) @classmethod def get_mission_political_donations(cls, username, mission_id): donation_amount = cache.get('%s_%s_donation_amount' % (username, mission_id)) beg_year = datetime(datetime.now(pytz.utc).year, 1, 1).strftime("%s") end_year = datetime(datetime.now(pytz.utc).year + 1, 1, 1 ).strftime("%s") if donation_amount == 0 or donation_amount is None: query = 'MATCH (p:Pleb {username: "******"})-[:DONATIONS_GIVEN]->' \ '(d:Donation)-[:DONATED_TO]->' \ '(c:Mission {object_uuid:"%s", ' \ 'focus_on_type: "position"}) ' \ 'WHERE d.created > %s AND d.created < %s ' \ 'RETURN sum(d.amount)' \ % (username, mission_id, beg_year, end_year) res, col = db.cypher_query(query) donation_amount = res.one if res.one is not None else 0 cache.set('%s_%s_donation_amount' % (username, mission_id), donation_amount) return donation_amount def get_quest(self): query = 'MATCH (p:Pleb {username: "******"})-[:IS_WAGING]->(c:Quest) ' \ 'RETURN c.owner_username' % self.username res, _ = db.cypher_query(query) return res.one def get_official_phone(self): query = 'MATCH (p:Pleb {username:"******"})-[:IS_AUTHORIZED_AS]->' \ '(o:PublicOfficial) RETURN o.gov_phone' % self.username res, _ = db.cypher_query(query) return res.one def deactivate(self): pass def get_address(self): query = 'MATCH (p:Pleb {username: "******"})-[:LIVES_AT]->(a:Address) ' \ 'RETURN a' % self.username res, _ = db.cypher_query(query) try: return Address.inflate(res.one) except AttributeError: return None def get_actions(self, cache_buster=False): actions = cache.get("%s_actions" % self.username) if actions is None or cache_buster is True: query = 'MATCH (a:Pleb {username: "******"})-' \ '[:CAN {active: true}]->(n:`SBAction`) ' \ 'RETURN n.resource' % self.username res, col = db.cypher_query(query) actions = [row[0] for row in res] cache.set("%s_actions" % self.username, actions) return actions def get_privileges(self, cache_buster=False): privileges = cache.get("%s_privileges" % self.username) if privileges is None or cache_buster is True: query = 'MATCH (a:Pleb {username: "******"})-' \ '[:HAS {active: true}]->(n:`Privilege`) ' \ 'RETURN n.name' % self.username res, col = db.cypher_query(query) privileges = [row[0] for row in res] cache.set("%s_privileges" % self.username, privileges) return privileges def get_badges(self): return self.badges.all() def get_full_name(self): return str(self.first_name) + " " + str(self.last_name) def update_quest(self): query = 'MATCH (p:Pleb {username:"******"})-[:IS_WAGING]->' \ '(c:Quest) SET c.first_name="%s", c.last_name="%s"' % \ (self.username, self.first_name, self.last_name) res, _ = db.cypher_query(query) return True def update_weight_relationship(self, sb_object, modifier_type): rel = self.object_weight.relationship(sb_object) if modifier_type in self.search_modifiers.keys(): rel.weight += self.search_modifiers[modifier_type] rel.status = modifier_type rel.save() return rel.weight def get_votable_content(self): from sb_base.neo_models import VotableContent query = 'MATCH (a:Pleb {username: "******"})<-[:OWNED_BY]-(' \ 'b:VotableContent) WHERE b.visibility = "public" RETURN b' \ '' % self.username res, col = db.cypher_query(query) return [VotableContent.inflate(row[0]) for row in res] def get_total_rep(self): rep_list = [] base_tags = {} tags = {} original_rep = self.reputation total_rep = 0 for item in self.get_votable_content(): rep_res = item.get_rep_breakout() if isinstance(rep_res, Exception) is True: return rep_res total_rep += rep_res['total_rep'] if 'base_tag_list' in rep_res.keys(): for base_tag in rep_res['base_tag_list']: base_tags[base_tag] = rep_res['rep_per_tag'] for tag in rep_res['tag_list']: tags[tag] = rep_res['rep_per_tag'] rep_list.append(rep_res) if total_rep < 0: total_rep = 0 self.reputation = total_rep self.save() cache.delete(self.username) return {"rep_list": rep_list, "base_tags": base_tags, "tags": tags, "total_rep": total_rep, "previous_rep": original_rep} def get_object_rep_count(self): pass def get_available_flags(self): pass def vote_on_content(self, content): pass def get_question_count(self): query = 'MATCH (pleb:Pleb {username:"******"})<-[:OWNED_BY]-' \ '(question:Question) RETURN COUNT(question)' % self.username res, _ = db.cypher_query(query) return res.one def get_solution_count(self): query = 'MATCH (pleb:Pleb {username:"******"})<-[:OWNED_BY]-' \ '(solution:Solution) RETURN COUNT(solution)' % self.username res, _ = db.cypher_query(query) return res.one def get_post_count(self): query = 'MATCH (pleb:Pleb {username:"******"})<-[:OWNED_BY]-' \ '(post:Post) RETURN COUNT(post)' % self.username res, _ = db.cypher_query(query) return res.one def get_comment_count(self): query = 'MATCH (pleb:Pleb {username:"******"})<-[:OWNED_BY]-' \ '(comment:Comment) RETURN COUNT(comment)' % self.username res, _ = db.cypher_query(query) return res.one def get_friends(self): return self.friends.all() def is_friends_with(self, username): query = "MATCH (a:Pleb {username:'******'})-" \ "[friend:FRIENDS_WITH]->(b:Pleb {username:'******'}) " \ "RETURN friend.active" % (self.username, username) res, col = db.cypher_query(query) if len(res) == 0: return False try: return res[0][0] except IndexError: return False def get_wall(self): """ Cypher Exception and IOError excluded on purpose, please do not add. The functions calling this expect the exceptions to be thrown and handle the exceptions on their own if they end up occurring. :return: """ from sb_wall.neo_models import Wall wall = cache.get("%s_wall" % self.username) if wall is None: query = "MATCH (a:Pleb {username:'******'})-" \ "[:OWNS_WALL]->(b:Wall) RETURN b" % self.username res, col = db.cypher_query(query) try: wall = Wall.inflate(res[0][0]) cache.set("%s_wall" % self.username, wall) except IndexError: # This may not be needed as the only way to get here is if # the wall was removed from the user or never created in the # first place. Since our tasks should never complete until a # wall has been created we shouldn't run into this. But to be # safe we've added it in. If we do manage to get here though # we may want to think about recovery methods or alerts we # should be sending out. return None return wall def determine_reps(self): from sb_public_official.utils import determine_reps return determine_reps(self) def get_donations(self): query = 'MATCH (p:Pleb {username: "******"})-[:DONATIONS_GIVEN]->' \ '(d:Donation)-[:CONTRIBUTED_TO]->(mission:Mission) ' \ 'RETURN d.object_uuid' % self.username res, col = db.cypher_query(query) return [row[0] for row in res] def get_sagebrew_donations(self): query = 'MATCH (p:`Pleb` {username: "******"})-[:DONATIONS_GIVEN]->' \ '(d:`Donation`) WHERE NOT (d)-[:CONTRIBUTED_TO]->(:Mission) ' \ 'AND NOT (d)-[:CONTRIBUTED_TO]->(:Quest) ' \ 'RETURN d.object_uuid' % self.username res, col = db.cypher_query(query) return [row[0] for row in res] def is_authorized_as(self): from sb_public_official.neo_models import PublicOfficial official = cache.get("%s_official" % self.username) if official is None: query = 'MATCH (p:Pleb {username: "******"})-[r:IS_AUTHORIZED_AS]->' \ '(o:PublicOfficial) WHERE r.active=true RETURN o' \ % self.username res, _ = db.cypher_query(query) try: official = PublicOfficial.inflate(res[0][0]) cache.set("%s_official" % self.username, official) except IndexError: official = None return official def is_following(self, username): query = 'MATCH (p:Pleb {username:"******"})<-[r:FOLLOWING]-' \ '(p2:Pleb {username:"******"}) RETURN r.active' % \ (self.username, username) res, _ = db.cypher_query(query) return res.one def follow(self, username): """ The username passed to this function is the user who will be following the user the method is called upon. This method is not idempotent on it's own. You must first call is_following to ensure the relationship doesn't already exist. :param username: """ query = 'MATCH (p:Pleb {username:"******"}), (p2:Pleb {username:"******"}) ' \ 'WITH p, p2 CREATE UNIQUE (p)<-[r:FOLLOWING]-(p2) SET ' \ 'r.active=true RETURN r.active' % (self.username, username) res, _ = db.cypher_query(query) return res.one def unfollow(self, username): """ The username passed to this function is the user who will stop following the user the method is called upon. :param username: """ query = 'MATCH (p:Pleb {username:"******"})<-[r:FOLLOWING]-(p2:Pleb ' \ '{username:"******"}) SET r.active=false RETURN r.active' \ % (self.username, username) res, _ = db.cypher_query(query) return res.one @property def reputation_change(self): # See create_vote_node task in sb_votes tasks for where this is deleted res = cache.get("%s_reputation_change" % self.username) if res is None: # last_counted_vote_node is set in sb_vote/tasks if it is None. query = 'MATCH (last_counted:Vote {object_uuid:"%s"})-' \ '[:CREATED_ON]->(s:Second) WITH s, last_counted MATCH ' \ '(s)-[:NEXT*]->(s2:Second)<-[:CREATED_ON]-(v:Vote)<-' \ '[:LAST_VOTES]-(content:VotableContent)-[:OWNED_BY]->' \ '(p:Pleb {username:"******"}) WITH v ORDER BY v.created DESC ' \ 'RETURN sum(v.reputation_change) ' \ 'as rep_change, collect(v.object_uuid)[0] as last_created' \ % (self.last_counted_vote_node, self.username) res, _ = db.cypher_query(query) if not res: return 0 # Have to cast to dict because pickle cannot handle the object # returned from cypher_query res = res[0].__dict__ cache.set("%s_reputation_change" % self.username, res) reputation_change = res['rep_change'] last_seen = res['last_created'] if last_seen != self.vote_from_last_refresh: self.vote_from_last_refresh = res['last_created'] self.save() cache.delete(self.username) if reputation_change >= 1000 or reputation_change <= -1000: return "%dk" % (int(reputation_change / 1000.0)) return reputation_change def get_political_parties(self): query = "MATCH (a:Pleb {username:'******'})-[:AFFILIATES_WITH]->" \ "(b:PoliticalParty) RETURN b.name" % self.username res, _ = db.cypher_query(query) return [row[0] for row in res] def get_activity_interests(self): query = "MATCH (a:Pleb {username:'******'})-[:WILL_PARTICIPATE]->" \ "(b:ActivityInterest) RETURN b.name" % self.username res, _ = db.cypher_query(query) return [row[0] for row in res] """
class Subject(StructuredNode): subject_name = StringProperty(unique_index=True) DBpediaURL = StringProperty() predicate = RelationshipTo(Object, 'predicate', model=RelationshipModel)
class PublicOfficial(Searchable): """ The PublicOfficial does not inherit from Pleb as Plebs must be associated with a user. PublicOfficial is a node that is dynamically populated based on our integrations with external services. It cannot be modified by users of the web app. Plebs can be attached to a PublicOfficial though if they are that individual. Public Officials should also always be available whether or not a user exits or joins the system so that we can always make the public information known and populate action areas for officials that are not yet signed up. """ first_name = StringProperty() last_name = StringProperty() middle_name = StringProperty() gender = StringProperty() date_of_birth = DateTimeProperty() gt_id = StringProperty(index=True) title = StringProperty() bio = StringProperty(default="") name_mod = StringProperty() current = BooleanProperty() district = IntegerProperty(default=0) state = StringProperty() website = StringProperty() start_date = DateTimeProperty() end_date = DateTimeProperty() full_name = StringProperty() gov_phone = StringProperty() # recipient_id and customer_id are stripe specific attributes recipient_id = StringProperty() customer_id = StringProperty() terms = IntegerProperty() twitter = StringProperty() youtube = StringProperty() bioguideid = StringProperty(unique_index=True) state_district = StringProperty() state_chamber = StringProperty() # address and contact form are gained from the term data and they will be # updated to their current office address and their current contact form address = StringProperty() contact_form = StringProperty() # bioguide is used to get the reps public profile picture # relationships pleb = RelationshipTo('plebs.neo_models.Pleb', 'AUTHORIZED_AS') # sponsored = RelationshipTo('sb_public_official.neo_models.Bill', # "SPONSORED") # co_sponsored = RelationshipTo('sb_public_official.neo_models.Bill', # "COSPONSORED") # proposed = RelationshipTo('sb_public_official.neo_models.Bill', # "PROPOSED") # hearings = RelationshipTo('sb_public_official.neo_models.Hearing', # "ATTENDED") # experience = RelationshipTo('sb_public_official.neo_models.Experience', # "EXPERIENCED") gt_person = RelationshipTo('govtrack.neo_models.GTPerson', 'GTPERSON') gt_role = RelationshipTo('govtrack.neo_models.GTRole', 'GTROLE') # the current term is also included in the number of terms under the term # relationship, the current_term relationship is a shortcut for us to # access the term that the official is currently in term = RelationshipTo('govtrack.neo_models.Term', 'SERVED_TERM') current_term = RelationshipTo('govtrack.neo_models.Term', 'CURRENT_TERM') quest = RelationshipTo('sb_quests.neo_models.Quest', 'IS_HOLDING') def get_quest(self): from sb_quests.neo_models import Quest # DEPRECATED use get_mission or get_quest instead # Not adding a deprecation warning as we cover this with the migration # command. Once that is executed we'll need to fix or remove this query = 'MATCH (o:PublicOfficial {object_uuid:"%s"})-' \ '[:IS_HOLDING]->(c:Quest) RETURN c' \ % self.object_uuid res, _ = db.cypher_query(query) if res.one: return Quest.inflate(res.one) else: return None
class Location(SBObject): name = StringProperty(index=True) # Valid Sectors: # state_upper - State Senator Districts # state_lower - State House Representative Districts # federal - U.S. Federal Districts (House of Reps) # local - Everything else :) sector = StringProperty(default=None) geo_data = StringProperty(default=None) encompasses = RelationshipTo('sb_locations.neo_models.Location', 'ENCOMPASSES') encompassed_by = RelationshipTo('sb_locations.neo_models.Location', 'ENCOMPASSED_BY') # Questions # Access Questions that are related to this location through: # Neomodel: focus_location Cypher: FOCUSED_ON # Mission # Access Missions that are related to this location through: # Neomodel: location Cypher: WITHIN # optimizations # TODO these might be best moved to the Question or maybe lat, long to since # we only need to create the marker on the display page # Allows us to determine which service to query with the id for additional # info # valid values: smarty_streets, google_maps created_by = StringProperty(default="smarty_streets") # ID provided by a third party representing the ID that should be used # when querying their service. external_id = StringProperty(default=None, index=True) @classmethod def get_encompasses(cls, object_uuid): query = 'MATCH (n:`Location` {object_uuid: "%s"})-' \ '[:ENCOMPASSES]->(e:`Location`) RETURN e.object_uuid' % ( object_uuid) res, _ = db.cypher_query(query) return [row[0] for row in res] @classmethod def get_encompassed_by(cls, object_uuid): query = 'MATCH (n:`Location` {object_uuid: "%s"})-' \ '[:ENCOMPASSED_BY]->(e:`Location`) RETURN e.object_uuid' % ( object_uuid) res, _ = db.cypher_query(query) return [row[0] for row in res] @classmethod def get_single_encompassed_by(cls, object_uuid): query = 'MATCH (n:`Location` {object_uuid: "%s"})-' \ '[:ENCOMPASSED_BY]->(e:`Location`) RETURN e.name' % ( object_uuid) res, _ = db.cypher_query(query) return res.one @classmethod def get_positions(cls, object_uuid): query = 'MATCH (l:`Location` {object_uuid: "%s"})-' \ '[:POSITIONS_AVAILABLE]->(p:`Position`) RETURN ' \ 'p.object_uuid' % object_uuid res, _ = db.cypher_query(query) return [row[0] for row in res]
class Publication(StructuredNode, NodeUtils): # Publication properties and relationships venueName = StringProperty() references = ArrayProperty() year = StringProperty() n_citation = StringProperty() venueID = StringProperty() fosWeights = ArrayProperty() authorNames = ArrayProperty() pubID = StringProperty(index=True) publisher = StringProperty() title = StringProperty() authorIDs = ArrayProperty() fosNames = ArrayProperty() doi = StringProperty() author = RelationshipTo('.author.Author', 'AUTHORED_BY') fos = RelationshipTo('.fos.FoS', 'IN_FIELD') publicationRel = RelationshipTo('.publication.Publication', 'REFERENCES') publisherRel = RelationshipTo('.publisher.Publisher', 'PUBLISHED_BY') venue = RelationshipTo('.venue.Venue', 'PUBLISHED_AT') @property def serialize(self): return { 'node_properties': { 'venueName': self.venueName, 'references': self.references, 'year': self.year, 'venueID': self.venueID, 'fosWeights': self.fosWeights, 'authorNames': self.authorNames, 'pubID': self.pubID, 'publisher': self.publisher, 'title': self.title, 'authorIDs': self.authorIDs, 'fosNames': self.fosNames, 'doi': self.doi, 'n_citation': self.n_citation }, } @property def serialize_connections(self): return [ { 'nodes_type': 'Publication', 'nodes_related': self.serialize_relationships(self.publication.all()), }, { 'nodes_type': 'Author', 'nodes_related': self.serialize_relationships(self.author.all()), }, { 'nodes_type': 'Venue', 'nodes_related': self.serialize_relationships(self.venue.all()), }, { 'nodes_type': 'Publisher', 'nodes_related': self.serialize_relationships(self.publisher.all()), }, { 'nodes_type': 'FoS', 'nodes_related': self.serialize_relationships(self.fos.all()) }, ]
class Disease(StructuredNode): name = StringProperty(unique_index=True, required=True) ar_name = StringProperty() description = StringProperty() ar_description = StringProperty() has = RelationshipTo(Symptom, 'has')
class User(StructuredNode): user_id = UniqueIdProperty() friends = Relationship('User', 'FRIEND') apps = RelationshipTo(App, 'OWNS', model=UserAppRel)
class Doctor(StructuredNode): name = StringProperty(unique_index=True, required=True) ar_name = StringProperty() description = StringProperty() ar_description = StringProperty() covers = RelationshipTo(Disease, 'covers')
class City(StructuredNode): name = StringProperty(unique_index=True) province = RelationshipTo(Province, "CityProvinceRel")
class User(StructuredNode): name = StringProperty() gender = StringProperty() pregnancy = BooleanProperty() group = StringProperty() might_have = RelationshipTo(Symptom, 'might_have')
class NeighborhoodNode(StructuredNode): name = StringProperty() community = RelationshipTo('DistrictNode', 'PART_OF') buildings = RelationshipFrom('BuildingNode', 'PART_OF')
class BugzillaBug(EstuaryStructuredNode): """Definition of a Bugzilla bug in Neo4j.""" assignee = RelationshipTo('.user.User', 'ASSIGNED_TO', cardinality=ZeroOrOne) attached_advisories = RelationshipFrom('.errata.Advisory', 'ATTACHED') classification = StringProperty() creation_time = DateTimeProperty() id_ = UniqueIdProperty(db_property='id') modified_time = DateTimeProperty(index=True) priority = StringProperty() # Called product_name in case we want to use product as a relationship later on product_name = StringProperty() product_version = StringProperty() qa_contact = RelationshipTo('.user.User', 'QA_BY', cardinality=ZeroOrOne) related_by_commits = RelationshipFrom('.distgit.DistGitCommit', 'RELATED') reporter = RelationshipTo('.user.User', 'REPORTED_BY', cardinality=ZeroOrOne) resolution = StringProperty() resolved_by_commits = RelationshipFrom('.distgit.DistGitCommit', 'RESOLVED') reverted_by_commits = RelationshipFrom('.distgit.DistGitCommit', 'REVERTED') severity = StringProperty() short_description = StringProperty() status = StringProperty() target_milestone = StringProperty() votes = IntegerProperty() @property def display_name(self): """Get intuitive (human readable) display name for the node.""" return 'RHBZ#{0}'.format(self.id_) @property def timeline_datetime(self): """Get the DateTime property used for the Estuary timeline.""" return self.creation_time @classmethod def find_or_none(cls, identifier): """ Find the node using the supplied identifier. :param str identifier: the identifier to search the node by :return: the node or None :rtype: EstuaryStructuredNode or None """ uid = identifier if uid.lower().startswith('rhbz'): uid = uid[4:] if uid.startswith('#'): uid = uid[1:] # If we are left with something other than digits, then it is an invalid identifier if not re.match(r'^\d+$', uid): raise ValidationError( '"{0}" is not a valid identifier'.format(identifier)) return cls.nodes.get_or_none(id_=uid)
class ApartmentNode(StructuredNode): number = StringProperty() floor = StringProperty() building = RelationshipTo(BuildingNode, 'PART_OF') persons = RelationshipFrom('PersonNode', 'LIVES_IN')
class Data(StructuredNode, DataInterface): """ Data Node The Data node provides arbitrary data in a key->value scheme and may be applicable to a user directly, or in relation to an application as well. """ # properties key = StringProperty(required=True) value = JSONProperty() # relationships # outgoing relationships (may or may not exist) applies_to_application = RelationshipTo('.application.Application', 'APPLIES_TO', cardinality=ZeroOrOne) # incoming relationships (should only ever be one of these) user = RelationshipFrom('.user.User', 'HAS_DATA', cardinality=ZeroOrOne) application = RelationshipFrom('.application.Application', 'HAS_DATA', cardinality=ZeroOrOne) organization = RelationshipFrom('.organization.Organization', 'HAS_DATA', cardinality=ZeroOrOne) group = RelationshipFrom('.group.Group', 'HAS_DATA', cardinality=ZeroOrOne) def to_object(self, *args, **kwargs) -> EData: data = EData(key=self.key, value=self.value) data._object = self return data @staticmethod def update_data_nodes(new_data, db_object, relationship): """ Update data nodes :param new_data: :param db_object: :param relationship: The name of the attribute defining the relationship to connect the data node to :return: """ if not hasattr(db_object, 'data') or not new_data: return existing_data = {d.to_object() for d in db_object.data.all()} existing_data_dict = {d.key: d for d in existing_data} for data_node in new_data: # they're the same if data_node in existing_data: continue # new value if data_node.key in existing_data_dict: existing_node = existing_data_dict.get(data_node.key) existing_node._object.value = data_node.value existing_node._object.save() # new node else: if data_node.key in ('_empty_', ''): continue data_node._object = Data(key=data_node.key, value=data_node.value) data_node._object.save() rel = getattr(data_node._object, relationship) rel.connect(db_object) # deletions existing_data_keys = set(existing_data_dict.keys()) new_data_keys = {d.key for d in new_data} deletions = existing_data_keys.difference(new_data_keys) for key in deletions: existing_data_dict[key]._object.delete()
class BadgeGroup(SBObject): name = StringProperty() description = StringProperty() # relationships badges = RelationshipTo('sb_badges.neo_models.Badge', "HAS")
class Donation(SBObject): """ Donations are contributions made from one user to another. Initially this will be utilized solely to provide a way to store the donations between a user and a user waging a campaign. Eventually we will be utilizing this or a derivative of it to also help track donations made to groups, higher reputation users, and projects. If a user's donation goes over the amount needed for the goal and the campaigner is on their first goal or has provided an update on the previous goal we release all the funds pledged, we do not attempt to break them up. However any donations pledged after that release will result in the same process of not being released until the next goal threshold is crossed and an update has been provided. If a donation is provided that spans x goals then the representative will need to provide x updates prior to receiving their next release """ # Whether or not the donation has been delivered or has just been pledged # False if Pledged and True if executed upon completed = BooleanProperty(default=False) # Set as a float to enable change to be specified. Even though from an # interface perspective we probably want to maintain that donations of # 5, 10, 100, etc are made. # Amount is an Integer to adhere to Stripe's API and to ensure precision # http://stackoverflow.com/questions/3730019/why-not-use-double-or- # float-to-represent-currency amount = IntegerProperty(required=True) # optimization owner_username = StringProperty() stripe_charge_id = StringProperty() mission_type = StringProperty() # Owner # Access who created this donation through: # Neomodel: donations Cypher: DONATIONS_GIVEN # RelationshipTo('plebs.neo_models.Pleb') # relationships mission = RelationshipTo('sb_missions.neo_models.Mission', "CONTRIBUTED_TO") quest = RelationshipTo('sb_quests.neo_models.Quest', "CONTRIBUTED_TO") # DEPRECATIONS # DEPRECATED: Rounds are deprecated and goals are no longer associated with # them. They are instead associated with Missions but are not aggregated # into rounds. owned_by = RelationshipTo('plebs.neo_models.Pleb', 'DONATED_FROM') @property def payment_method(self): # DO NOT USE: NON-USE PLACEHOLDER FOR SERIALIZER return None @classmethod def get_mission(cls, object_uuid): query = 'MATCH (d:Donation {object_uuid: "%s"})-' \ '[:CONTRIBUTED_TO]->(mission:Mission) ' \ 'RETURN mission.object_uuid' % object_uuid res, _ = db.cypher_query(query) return res.one @classmethod def get_quest(cls, object_uuid): query = 'MATCH (d:Donation {object_uuid: "%s"})-' \ '[:CONTRIBUTED_TO]->(mission:Mission)<-[:EMBARKS_ON]-' \ '(quest:Quest) ' \ 'RETURN quest.object_uuid' % object_uuid res, _ = db.cypher_query(query) return res.one @classmethod def get_owner(cls, object_uuid): query = 'MATCH (d:`Donation` {object_uuid: "%s"}) ' \ 'RETURN d.owner_username' % object_uuid res, col = db.cypher_query(query) try: return res[0][0] except IndexError: return None
class Country(StructuredNode): code = StringProperty(unique_index=True) inhabitant = RelationshipFrom(Person, 'IS_FROM') president = RelationshipTo(Person, 'PRESIDENT', cardinality=One)
class Synopsis(StructuredNode): uid=UniqueIdProperty() summarizes = RelationshipTo('Session','SUMMARIZES') summarized = RelationshipFrom('Student','SUMMARIZED_BY')