Expanded Team Social Account Links #1320

Closed
wants to merge 15 commits into
from

Projects

None yet

4 participants

@phil-lopreiato
Member

Something we've been talking about doing for a long time and I've slowly been hacking away at.

Support for adding links to social accounts with a similar suggestion/storage model as media. Currently allows FB, Twitter, YouTube, and GitHub profile links.

This PR doesn't display connections on team pages yet, but that's next on my list.
screenshot from 2015-12-31 17-23-01

screenshot from 2015-12-31 17-41-00

@gregmarra gregmarra commented on an outdated diff Dec 31, 2015
helpers/social_connection_helper.py
+ return social_dict
+
+ @classmethod
+ def _partial_social_dict_from_github(cls, url):
+ social_dict = {}
+ social_dict['social_type_enum'] = SocialConnectionType.GITHUB
+ foreign_key = cls._parse_github_foreign_key(url)
+ if foreign_key is None:
+ logging.warning("Failed to determine foreign_key from url: {}".format(url))
+ return None
+ social_dict['foreign_key'] = foreign_key
+
+ return social_dict
+
+ @classmethod
+ def _parse_facebook_foreign_key(cls, url):
@gregmarra
gregmarra Dec 31, 2015 Contributor

These methods feel repetitive. Is there a way we can abstract them more by passing in a regex?

@gregmarra gregmarra commented on an outdated diff Dec 31, 2015
helpers/social_connection_helper.py
+ conns_by_type[slug_name] = [conn]
+ return conns_by_type
+
+
+class SocialConnectionParser(object):
+ FACEBOOK_URL_PATTERNS = ['facebook.com/']
+ TWITTER_URL_PATTERNS = ['twitter.com/']
+ YOUTUBE_URL_PATTERNS = ['youtube.com/user/']
+ GITHUB_URL_PATTERNS = ['github.com/']
+
+ @classmethod
+ def partial_social_dict_from_url(cls, url):
+ """
+ Takes a url, and turns it into a partial Media object dict
+ """
+ if any(s in url for s in cls.FACEBOOK_URL_PATTERNS):
@gregmarra
gregmarra Dec 31, 2015 Contributor

Is there a way to put all the patterns in a list (maybe initially have them all in a dict?) and iterate through it? This feels repetitive. It seems like you'd need to touch code in a lot of places to add a new pattern.

@gregmarra gregmarra commented on an outdated diff Dec 31, 2015
templates/social_partials/facebook_partial.html
@@ -0,0 +1,3 @@
+<div>
@gregmarra
gregmarra Dec 31, 2015 Contributor

Why does each social need it's own partial? They all have the same structure. Why not just an abstract {{ connection.profile_url }} and the connection handles generating it?

@gregmarra gregmarra commented on the diff Dec 31, 2015
templates/suggest_social_connection.html
+ <div class="row">
+ <div class="col-xs-12 col-lg-6 col-lg-offset-3">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h1 class="panel-title">Add a team's social account to The Blue Alliance!</h1>
+ </div>
+ <div class="panel-body">
+ <p>Thanks for helping make The Blue Alliance better! Let us know about a social account so we can add them to the site!</p>
+ <ul>
+ <li>Your suggestion will be reviewed by a moderator</li>
+ <li>Your account info (like {{user_bundle.user.email}} and {{user_bundle.user.nickname}}) will also be submitted. <a href="{{user_bundle.logout_url}}">log in as a different user</a></li>
+ </ul>
+
+ <hr>
+
+ <p><strong>Currently supported formats are:</strong></p>
@gregmarra
gregmarra Dec 31, 2015 Contributor

If you wanted to get fancy, you could generate this from your Type const class.

@gregmarra gregmarra commented on the diff Dec 31, 2015
models/social_connection.py
@@ -0,0 +1,69 @@
+import json
@gregmarra
gregmarra Dec 31, 2015 Contributor

Why have this as a join model instead of a dict property on Team objects? I'm not sure which approach would ultimately be cleaner.

@phil-lopreiato
phil-lopreiato Jan 1, 2016 Member

The idea is that we can eventually link to other models that aren't Team. For example, a lot of the New England district events have their own twitter profiles (example: https://twitter.com/wtbydistrict). So it'll be cleaner, I think, to keep one join model that can reference multiple types of parent

@fangeugene
fangeugene Jan 1, 2016 Member

Then why don't we have different Website models?

@phil-lopreiato
phil-lopreiato Jan 1, 2016 Member

Do you think it would be better to do as dicts in each model? I thought we were trying to move away from those JSON properties because it made more complex queries and mutations more difficult. I'm open to changing it if we think it's better done by a different approach, though

@fangeugene
fangeugene Jan 1, 2016 Member

I'm not sure, I was just playing devil's advocate.

One way to decide would be to think about how we would query them. I'm a fan of the JSON property because I don't think we would ever query for anything other than "get me all social connections for team XXX." e.g. we would never do "get me all Facebook connections for event XXX (which would us to make an intermediate query for Teams anyways)." Also, unlike Robots, the number of social connections doesn't increase over time for one Team.

@phil-lopreiato
phil-lopreiato Jan 1, 2016 Member

That's a good point. I'd agree the strongest argument in favor of doing it as a JSON property is that the relationship will always be 1:1 (connection of a specific type : parent model, eg team) and that we'll only query for connections associated with a given team, like you said.

Maybe I'll give it a shot tomorrow and see how it looks that way

@fangeugene
fangeugene Jan 1, 2016 Member

To add to your 1:1 argument, Robots are 1:1 too (which we have separate Models for), but they grow linearly with the number of years. Connections don't.

@gregmarra
gregmarra Jan 1, 2016 Contributor

I could see a single team sometimes having more than one github or more than one instagram, although I agree this wouldn't be super common.

I think the biggest argument for this join model is that we can use these for Teams, Events, Districts etc. That will let us easily re-use the same model and templates and such with less duplicated code. If we wanted to store more complicated data about a SocialConnection (like scraping posts? last updated time? fan counts?) we'd be able to store them on these models as well.

@chacha
chacha Jan 1, 2016 Contributor

👍 for being able to use this for other entities. It does seem like everything on FIRST is getting their own social media which we should be able to point to.

Let's just avoid creating Twitter accounts for individual matches. That might be a bit excessive.

@gregmarra gregmarra commented on an outdated diff Dec 31, 2015
models/social_connection.py
+ def __init__(self, *args, **kw):
+ super(SocialConnection, self).__init__(*args, **kw)
+
+ @property
+ def slug_name(self):
+ return self.SLUG_NAMES[self.social_type_enum]
+
+ @classmethod
+ def create_reference(self, reference_type, reference_key):
+ return ndb.Key(self.REFERENCE_MAP[reference_type], reference_key)
+
+ @property
+ def key_name(self):
+ return self.render_key_name(self.social_type_enum, self.foreign_key)
+
+ # URL Renderers
@gregmarra
gregmarra Dec 31, 2015 Contributor

Since each of these are just a pattern with a foreign_key passed in, why not have them all defined as consts and have single function?

@gregmarra
Contributor

I love this! We can also use social plugins to add a Twitter stream, Facebook stream, etc.

This is going to need some adjustments after I land #1306. I apologize for leaving this in limbo so long – I should try to get it merged this weekend.

A few other comments inline, mostly to try to abstract things to make extending this later easier.

@fangeugene
Member

Good stuff.

@gregmarra
Contributor

I just landed #1306, so this should be reworked to follow those patterns for suggestions.

@phil-lopreiato
Member

Okay, master is all merged in and suggestions don't require admin to approve now. I think this pull is ready to go, I'll add in exposing this data in a separate diff.

I left the storage as a join model. I think that it's the best choice,mainly because it makes future modularity much simpler, especially when adding social connections to other models and provides an easy way to store metadata about those connections. Does anybody feel differently?

@phil-lopreiato phil-lopreiato changed the title from [RFC] Expanded Team Social Account Links to Expanded Team Social Account Links Jan 1, 2016
@chacha
Contributor
chacha commented Jan 2, 2016

👍

@gregmarra
Contributor

Thinking about this more, why not re-use the Media class here?

https://github.com/the-blue-alliance/the-blue-alliance/blob/master/models/media.py

It has the same shape: a reference and a foreign key.

@phil-lopreiato
Member

They're similar classes in structure, but would it be confusing to make it so a Media object could refer to either a image/video or a social networking profile? They seem like two disjoint use cases.

Can you think of a better name for Media that would eliminate this problem?

@gregmarra
Contributor

Link :)

How would we want to represent each of these?

  • A YouTube video
  • A YouTube profile
  • A website
  • A Facebook Event
  • A webcast's location

I guess these are all "references to foreign keys on other sites with some logic based on type".

@gregmarra gregmarra commented on the diff Jan 31, 2016
consts/social_connection_type.py
@@ -0,0 +1,23 @@
+class SocialConnectionType(object):
+ """
+ Constants for social connections
+ """
+
+ FACEBOOK = 0
+ TWITTER = 1
+ YOUTUBE = 2
+ GITHUB = 3
+
+ type_names = {
@gregmarra
gregmarra Jan 31, 2016 Contributor

Should this be caps, as a constant?

@phil-lopreiato
Member

So we can have one Link model that handles

  • YouTube Videos
  • CD Photos
  • FB Events
  • Team/Event Websites (? - Not sure about this one, there isn't really associated metadata we'd care about)
  • Webcasts
  • Other Social Profiles

I could see that working, and probably be cleaner across the codebase when compared to the multiple solutions that currently exist. Thoughts @fangeugene?

@gregmarra
Contributor

I think my main objection right now is that SocialProfile seems to be a copy-paste of Media :)

@phil-lopreiato
Member

Fair point :)

I can refactor this to include:

  • Your changes from #1350
  • Media -> Link and use it just for social connections for now
@gregmarra
Contributor

@phil-lopreiato do you know the implications of renaming a Model on an appengine app? I bet it breaks everything :/

@fangeugene
Member

It does break everything.

@phil-lopreiato
Member

Shoot. Should I just use Media then?

@gregmarra
Contributor

@phil-lopreiato I think using the Media class to represent "stuff out on the web" will work fine. A social media is a media, after all :)

What would be the right pattern for taking a Media and making, like, a YoutubeMedia object? It seems like we could somehow have more specific classes that understand better the semantics of those properties, vs having tons of helper functions inside of the base Media class. Ideally everything would just be stored as a Media, but we could have these children classes that have superpowers.

@phil-lopreiato
Member

Not sure about that. Would subclasses play nice with ndb? Or should we go with some helper functions to go from Media to some dict that can be different based on type

@gregmarra
Contributor

Let's go with Media for this. I think the format is basically correct, and we can just use it as a generic foreign key mapper to our own models. I'm not worried about the slightly confusing name.

@phil-lopreiato
Member

Sounds good, I'll get this diff refactored and ready to ship this week.

@phil-lopreiato phil-lopreiato referenced this pull request Mar 4, 2016
Merged

Team Social Media! #1389

@phil-lopreiato
Member

Closing in favor of #1389

@phil-lopreiato phil-lopreiato deleted the phil-lopreiato:social-model branch Aug 5, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment