sharing for collections (bug 586444)
This commit is contained in:
Родитель
a10c20bd06
Коммит
ec4c45d614
|
@ -15,8 +15,9 @@ import amo.models
|
|||
from amo.fields import DecimalCharField
|
||||
from amo.utils import urlparams, sorted_groupby, JSONEncoder
|
||||
from amo.urlresolvers import reverse
|
||||
from cake.urlresolvers import remora_url
|
||||
from reviews.models import Review
|
||||
from stats.models import Contribution as ContributionStats, ShareCountTotal
|
||||
from stats.models import Contribution as ContributionStats, AddonShareCountTotal
|
||||
from translations.fields import (TranslatedField, PurifiedField,
|
||||
LinkifiedField, translations_with_fallback)
|
||||
from users.models import UserProfile, PersonaAuthor
|
||||
|
@ -227,6 +228,9 @@ class Addon(amo.models.ModelBase):
|
|||
"""The url for this add-on's AddonType."""
|
||||
return AddonType(self.type).get_url_path()
|
||||
|
||||
def share_url(self):
|
||||
return remora_url('/addon/share/%s' % self.id)
|
||||
|
||||
@amo.cached_property(writable=True)
|
||||
def listed_authors(self):
|
||||
return UserProfile.objects.filter(addons=self,
|
||||
|
@ -469,7 +473,7 @@ class Addon(amo.models.ModelBase):
|
|||
@caching.cached_method
|
||||
def share_counts(self):
|
||||
rv = collections.defaultdict(int)
|
||||
rv.update(ShareCountTotal.objects.filter(addon=self)
|
||||
rv.update(AddonShareCountTotal.objects.filter(addon=self)
|
||||
.values_list('service', 'count'))
|
||||
return rv
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@
|
|||
{% include 'addons/includes/collection_add_widget.html' %}
|
||||
{% endif %}
|
||||
|
||||
{{ addon_sharing(addon) }}
|
||||
{{ sharing_box(addon) }}
|
||||
|
||||
</div>{# /secondary #}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
{% endif %}
|
||||
|
||||
{# TODO(davedash): Remove until zamboni does sharing
|
||||
{{ addon_sharing(addon) }}
|
||||
{{ sharing_box(addon) }}
|
||||
#}
|
||||
|
||||
</div></div>{# /addon-summary and -wrapper #}
|
||||
|
|
|
@ -13,7 +13,7 @@ from cake.models import Session
|
|||
from devhub.models import AddonLog, LOG as ADDONLOG
|
||||
from files.models import TestResult, TestResultCache
|
||||
from sharing.models import SERVICES
|
||||
from stats.models import ShareCount, Contribution
|
||||
from stats.models import AddonShareCount, Contribution
|
||||
|
||||
log = commonware.log.getLogger('z.cron')
|
||||
|
||||
|
@ -33,7 +33,7 @@ def gc(test_result=True):
|
|||
Session.objects.filter(expires__lt=two_days_ago_unixtime).delete()
|
||||
|
||||
log.debug('Cleaning up sharing services.')
|
||||
ShareCount.objects.exclude(
|
||||
AddonShareCount.objects.exclude(
|
||||
service__in=[s.shortname for s in SERVICES]).delete()
|
||||
|
||||
# XXX(davedash): I can't seem to run this during testing without triggering
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
},
|
||||
{
|
||||
"pk": 2567,
|
||||
"model": "stats.sharecount",
|
||||
"model": "stats.addonsharecount",
|
||||
"fields": {
|
||||
"count": 1,
|
||||
"date": "2009-07-27",
|
||||
|
|
|
@ -5,7 +5,7 @@ from bandwagon.models import Collection
|
|||
from cake.models import Session
|
||||
from devhub.models import AddonLog
|
||||
from files.models import TestResult, TestResultCache
|
||||
from stats.models import ShareCount, Contribution
|
||||
from stats.models import AddonShareCount, Contribution
|
||||
from amo.cron import gc
|
||||
|
||||
|
||||
|
@ -19,7 +19,7 @@ class GarbageTest(test_utils.TestCase):
|
|||
eq_(AddonLog.objects.all().count(), 1)
|
||||
eq_(TestResult.objects.all().count(), 1)
|
||||
eq_(TestResultCache.objects.all().count(), 1)
|
||||
eq_(ShareCount.objects.all().count(), 1)
|
||||
eq_(AddonShareCount.objects.all().count(), 1)
|
||||
eq_(Contribution.objects.all().count(), 1)
|
||||
gc(test_result=False)
|
||||
eq_(Collection.objects.all().count(), 0)
|
||||
|
@ -28,5 +28,5 @@ class GarbageTest(test_utils.TestCase):
|
|||
# XXX(davedash): this isn't working in testing.
|
||||
# eq_(TestResult.objects.all().count(), 0)
|
||||
eq_(TestResultCache.objects.all().count(), 0)
|
||||
eq_(ShareCount.objects.all().count(), 0)
|
||||
eq_(AddonShareCount.objects.all().count(), 0)
|
||||
eq_(Contribution.objects.all().count(), 0)
|
||||
|
|
|
@ -16,8 +16,9 @@ from amo.utils import sorted_groupby
|
|||
from amo.urlresolvers import reverse
|
||||
from addons.models import Addon, AddonRecommendation
|
||||
from applications.models import Application
|
||||
from users.models import UserProfile
|
||||
from stats.models import CollectionShareCountTotal
|
||||
from translations.fields import TranslatedField, LinkifiedField
|
||||
from users.models import UserProfile
|
||||
|
||||
SPECIAL_SLUGS = amo.COLLECTION_SPECIAL_SLUGS
|
||||
|
||||
|
@ -177,6 +178,10 @@ class Collection(amo.models.ModelBase):
|
|||
return reverse('collections.delete',
|
||||
args=[self.author_username, self.slug])
|
||||
|
||||
def share_url(self):
|
||||
return reverse('collections.share',
|
||||
args=[self.author_username, self.slug])
|
||||
|
||||
@property
|
||||
def author_username(self):
|
||||
return self.author.username if self.author else 'anonymous'
|
||||
|
@ -203,6 +208,13 @@ class Collection(amo.models.ModelBase):
|
|||
else:
|
||||
return settings.MEDIA_URL + 'img/amo2009/icons/collection.png'
|
||||
|
||||
@caching.cached_method
|
||||
def share_counts(self):
|
||||
rv = collections.defaultdict(int)
|
||||
rv.update(CollectionShareCountTotal.objects.filter(collection=self)
|
||||
.values_list('service', 'count'))
|
||||
return rv
|
||||
|
||||
def get_recommendations(self):
|
||||
"""Get a collection of recommended add-ons for this collection."""
|
||||
if self.recommended_collection:
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
<a title="{{ _('Share this Collection') }}" class="share" href="#"></a>
|
||||
<a title="{{ _('Copy this Collection') }}" class="copy" href="#"></a>
|
||||
#}
|
||||
{{ sharing_box(c, show_email=False) }}
|
||||
{% if request.check_ownership(c, require_owner=False) %}
|
||||
<a title="{{ _('Edit this Collection') }}" class="edit" href="{{ c.edit_url() }}"></a>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import urlparse
|
||||
|
||||
import django.test
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
|
@ -670,3 +671,25 @@ class TestWatching(test_utils.TestCase):
|
|||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
eq_(r.status_code, 200)
|
||||
eq_(json.loads(r.content), {'watching': True})
|
||||
|
||||
|
||||
class TestSharing(test_utils.TestCase):
|
||||
fixtures = ['base/collection_57181']
|
||||
|
||||
def test_twitter_share(self):
|
||||
c = Collection.objects.get(id=57181)
|
||||
r = self.client.get(c.share_url() + '?service=twitter')
|
||||
eq_(r.status_code, 302)
|
||||
loc = urlparse.urlparse(r['Location'])
|
||||
query = dict(urlparse.parse_qsl(loc.query))
|
||||
eq_(loc.netloc, 'twitter.com')
|
||||
status = 'Home Business Auto :: Add-ons for Firefox'
|
||||
assert status in query['status'], query['status']
|
||||
|
||||
def test_404(self):
|
||||
c = Collection.objects.get(id=57181)
|
||||
url = reverse('collections.share', args=[c.author.username, c.slug])
|
||||
r = self.client.get(url)
|
||||
eq_(r.status_code, 404)
|
||||
r = self.client.get(url + '?service=xxx')
|
||||
eq_(r.status_code, 404)
|
||||
|
|
|
@ -19,6 +19,7 @@ detail_urls = patterns('',
|
|||
url('^(?P<action>add|remove)$', views.collection_alter,
|
||||
name='collections.alter'),
|
||||
url('^watch$', views.watch, name='collections.watch'),
|
||||
url('^share$', views.share, name='collections.share'),
|
||||
)
|
||||
|
||||
ajax_urls = patterns('',
|
||||
|
|
|
@ -11,6 +11,7 @@ import caching.base as caching
|
|||
from tower import ugettext_lazy as _lazy, ugettext as _
|
||||
|
||||
import amo.utils
|
||||
import sharing.views
|
||||
from amo.decorators import login_required, post_required, json_view
|
||||
from amo.urlresolvers import reverse
|
||||
from access import acl
|
||||
|
@ -455,3 +456,10 @@ def watch(request, username, slug):
|
|||
return {'watching': watching}
|
||||
else:
|
||||
return redirect(collection.get_url_path())
|
||||
|
||||
|
||||
def share(request, username, slug):
|
||||
collection = get_collection(request, username, slug)
|
||||
return sharing.views.share(request, collection,
|
||||
name=collection.name,
|
||||
description=collection.description)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[
|
||||
{
|
||||
"pk": 177155738,
|
||||
"model": "stats.sharecounttotal",
|
||||
"model": "stats.addonsharecounttotal",
|
||||
"fields": {
|
||||
"count": 13,
|
||||
"service": "delicious",
|
||||
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
{
|
||||
"pk": 177155739,
|
||||
"model": "stats.sharecounttotal",
|
||||
"model": "stats.addonsharecounttotal",
|
||||
"fields": {
|
||||
"count": 29,
|
||||
"service": "digg",
|
||||
|
@ -19,7 +19,7 @@
|
|||
},
|
||||
{
|
||||
"pk": 177155741,
|
||||
"model": "stats.sharecounttotal",
|
||||
"model": "stats.addonsharecounttotal",
|
||||
"fields": {
|
||||
"count": 4,
|
||||
"service": "friendfeed",
|
||||
|
@ -28,7 +28,7 @@
|
|||
},
|
||||
{
|
||||
"pk": 177155742,
|
||||
"model": "stats.sharecounttotal",
|
||||
"model": "stats.addonsharecounttotal",
|
||||
"fields": {
|
||||
"count": 14,
|
||||
"service": "myspace",
|
||||
|
@ -37,7 +37,7 @@
|
|||
},
|
||||
{
|
||||
"pk": 177155743,
|
||||
"model": "stats.sharecounttotal",
|
||||
"model": "stats.addonsharecounttotal",
|
||||
"fields": {
|
||||
"count": 4,
|
||||
"service": "twitter",
|
||||
|
|
|
@ -6,29 +6,33 @@ from amo.helpers import login_link
|
|||
from .models import ServiceBase, EMAIL
|
||||
|
||||
|
||||
@register.inclusion_tag('sharing/addon_sharing.html')
|
||||
@register.inclusion_tag('sharing/sharing_box.html')
|
||||
@jinja2.contextfunction
|
||||
def addon_sharing(context, addon):
|
||||
# prepare services
|
||||
def sharing_box(context, obj, show_email=True):
|
||||
request = context['request']
|
||||
opts = {}
|
||||
services = list(sharing.SERVICES_LIST)
|
||||
if not show_email:
|
||||
services.remove(EMAIL)
|
||||
for service in sharing.SERVICES_LIST:
|
||||
service_opts = {}
|
||||
if service == EMAIL and not context['request'].user.is_authenticated():
|
||||
if service == EMAIL and not request.user.is_authenticated():
|
||||
service_opts['url'] = login_link(context)
|
||||
service_opts['target'] = '_self'
|
||||
else:
|
||||
service_opts['url'] = '/addon/share/{id}?service={name}'.format(
|
||||
id=addon.id, name=service.shortname)
|
||||
url = obj.share_url() + '?service=%s' % service.shortname
|
||||
service_opts['url'] = url
|
||||
service_opts['target'] = '_blank'
|
||||
opts[service] = service_opts
|
||||
|
||||
c = dict(context.items())
|
||||
c.update({
|
||||
'request': context['request'],
|
||||
'user': context['request'].user,
|
||||
'addon': addon,
|
||||
'services': sharing.SERVICES_LIST,
|
||||
'request': request,
|
||||
'user': request.user,
|
||||
'obj': obj,
|
||||
'services': services,
|
||||
'service_opts': opts,
|
||||
'email_service': EMAIL,
|
||||
'show_email': show_email,
|
||||
})
|
||||
return c
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from tower import ugettext_lazy as _, ungettext as ngettext
|
||||
from stats.models import ShareCountTotal
|
||||
from stats.models import AddonShareCountTotal
|
||||
|
||||
|
||||
# string replacements in URLs are: url, title, description
|
||||
|
@ -16,16 +16,16 @@ class DELICIOUS(ServiceBase):
|
|||
"""see: http://delicious.com/help/savebuttons"""
|
||||
shortname = 'delicious'
|
||||
label = _(u'Add to Delicious')
|
||||
url = ('http://delicious.com/save?url={url}&title={title}'
|
||||
'¬es={description}')
|
||||
url = (u'http://delicious.com/save?url={url}&title={title}'
|
||||
'¬es={description}')
|
||||
|
||||
|
||||
class DIGG(ServiceBase):
|
||||
"""see: http://digg.com/tools/integrate#3"""
|
||||
shortname = 'digg'
|
||||
label = _(u'Digg this!')
|
||||
url = ('http://digg.com/submit?url={url}&title={title}&bodytext='
|
||||
'{description}&media=news&topic=tech_news')
|
||||
url = (u'http://digg.com/submit?url={url}&title={title}&bodytext='
|
||||
'{description}&media=news&topic=tech_news')
|
||||
|
||||
@staticmethod
|
||||
def count_term(count):
|
||||
|
@ -36,14 +36,14 @@ class FACEBOOK(ServiceBase):
|
|||
"""see: http://www.facebook.com/share_options.php"""
|
||||
shortname = 'facebook'
|
||||
label = _(u'Post to Facebook')
|
||||
url = 'http://www.facebook.com/share.php?u={url}&t={title}'
|
||||
url = u'http://www.facebook.com/share.php?u={url}&t={title}'
|
||||
|
||||
|
||||
class FRIENDFEED(ServiceBase):
|
||||
"""see: http://friendfeed.com/embed/link"""
|
||||
shortname = 'friendfeed'
|
||||
label = _(u'Share on FriendFeed')
|
||||
url = 'http://friendfeed.com/?url={url}&title={title}'
|
||||
url = u'http://friendfeed.com/?url={url}&title={title}'
|
||||
|
||||
@staticmethod
|
||||
def count_term(count):
|
||||
|
@ -54,14 +54,14 @@ class MYSPACE(ServiceBase):
|
|||
"""see: http://www.myspace.com/posttomyspace"""
|
||||
shortname = 'myspace'
|
||||
label = _(u'Post to MySpace')
|
||||
url = ('http://www.myspace.com/index.cfm?fuseaction=postto&t={title}'
|
||||
'&c={description}&u={url}&l=1')
|
||||
url = (u'http://www.myspace.com/index.cfm?fuseaction=postto&t={title}'
|
||||
'&c={description}&u={url}&l=1')
|
||||
|
||||
|
||||
class TWITTER(ServiceBase):
|
||||
shortname = 'twitter'
|
||||
label = _(u'Post to Twitter')
|
||||
url = 'https://twitter.com/home?status={title}%20{title}'
|
||||
url = u'https://twitter.com/home?status={title}%20{url}'
|
||||
|
||||
@staticmethod
|
||||
def count_term(count):
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
<div class="share-this">
|
||||
<a class="share" href="#">{{ _('Share this Add-on') }}</a>
|
||||
{% if obj.__class__.__name__ == 'Addon' %}
|
||||
<a class="share" href="#">{{ _('Share this Add-on') }}</a>
|
||||
{% elif obj.__class__.__name__ == 'Collection' %}
|
||||
<a class="share" href="#">{{ _('Share this Collection') }}</a>
|
||||
{% endif %}
|
||||
<div class="share-arrow"><div class="share-frame">
|
||||
{% cache addon %}
|
||||
{% cache obj %}
|
||||
<div class="share-networks share-content">
|
||||
<ul>
|
||||
{% for service in services %}
|
||||
{% set opts = service_opts[service] %}
|
||||
{% set share_counts = obj.share_counts() %}
|
||||
<li class="{{ service.shortname }}">
|
||||
<span class="share-link">
|
||||
<a class="uniquify" target="{{ opts.target }}" href="{{ opts.url }}">
|
||||
|
@ -13,7 +18,7 @@
|
|||
</a>
|
||||
</span>
|
||||
<span class="share-count">
|
||||
{{ service.count_term(addon.share_counts()[service.shortname]) }}
|
||||
{{ service.count_term(share_counts[service.shortname]) }}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
@ -21,7 +26,7 @@
|
|||
</div>{# /share-networks #}
|
||||
{% endcache %}
|
||||
|
||||
{% if request.user.is_authenticated() %}
|
||||
{% if request.user.is_authenticated() and show_email %}
|
||||
<div class="share-email share-content">
|
||||
<form action="{{ remora_url(service_opts[email_service].url) }}"
|
||||
method="post">
|
|
@ -2,7 +2,7 @@ from datetime import date, timedelta
|
|||
|
||||
from django import test
|
||||
from django.contrib.auth.models import User as DjangoUser
|
||||
from django.utils import translation
|
||||
from django.utils import translation, encoding
|
||||
|
||||
import jingo
|
||||
from mock import Mock
|
||||
|
@ -12,15 +12,15 @@ from pyquery import PyQuery as pq
|
|||
from addons.models import Addon
|
||||
import amo
|
||||
import sharing
|
||||
from sharing.helpers import addon_sharing
|
||||
from sharing.helpers import sharing_box
|
||||
from sharing.models import DIGG, FACEBOOK
|
||||
from stats.models import ShareCount
|
||||
from stats.models import AddonShareCount
|
||||
|
||||
|
||||
class SharingHelpersTestCase(test.TestCase):
|
||||
fixtures = ['base/addon_3615']
|
||||
|
||||
def test_addon_sharing(self):
|
||||
def test_sharing_box(self):
|
||||
addon = Addon.objects.get(id=3615)
|
||||
|
||||
jingo.load_helpers()
|
||||
|
@ -37,7 +37,7 @@ class SharingHelpersTestCase(test.TestCase):
|
|||
cake_csrf_token.__name__ = 'cake_csrf_token'
|
||||
jingo.register.function(cake_csrf_token)
|
||||
|
||||
doc = pq(addon_sharing(ctx, addon))
|
||||
doc = pq(sharing_box(ctx, addon))
|
||||
self.assert_(doc.html())
|
||||
self.assertEquals(doc('li').length, len(sharing.SERVICES_LIST))
|
||||
|
||||
|
@ -60,3 +60,27 @@ class SharingModelsTestCase(test.TestCase):
|
|||
# total count with no shares
|
||||
eq_(addon.share_counts()[FACEBOOK.shortname], 0,
|
||||
'Total count with no shares must be 0')
|
||||
|
||||
|
||||
def test_services_unicode():
|
||||
u = u'\u05d0\u05d5\u05e1\u05e3'
|
||||
d = dict(title=u, url=u, description=u)
|
||||
for service in sharing.SERVICES_LIST:
|
||||
if service.url:
|
||||
service.url.format(**d)
|
||||
# This does not work since Python tries to use ascii to decode the string.
|
||||
# d = dict((k, encoding.smart_str(v)) for k, v in d.items())
|
||||
# for service in sharing.SERVICES_LIST:
|
||||
# if service.url:
|
||||
# service.url.format(**d)
|
||||
|
||||
|
||||
def test_share_view():
|
||||
u = u'\u05d0\u05d5\u05e1\u05e3'
|
||||
s = encoding.smart_str(u)
|
||||
request, obj = Mock(), Mock()
|
||||
request.GET = {'service': 'twitter'}
|
||||
obj.get_url_path.return_value = u
|
||||
sharing.views.share(request, obj, u, u)
|
||||
obj.get_url_path.return_value = s
|
||||
sharing.views.share(request, obj, s, s)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
from django import http
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.encoding import smart_unicode as u
|
||||
|
||||
from amo.helpers import page_title, absolutify
|
||||
import sharing
|
||||
|
||||
|
||||
def share(request, obj, name, description):
|
||||
try:
|
||||
service = sharing.SERVICES[request.GET['service']]
|
||||
except KeyError:
|
||||
raise http.Http404()
|
||||
d = {
|
||||
'title': page_title({'request': request}, name),
|
||||
'description': u(description),
|
||||
'url': absolutify(u(obj.get_url_path())),
|
||||
}
|
||||
return redirect(service.url.format(**d))
|
|
@ -88,7 +88,7 @@ class UpdateCount(caching.base.CachingMixin, models.Model):
|
|||
return ['*/addon/%d/statistics/usage*' % self.addon_id, ]
|
||||
|
||||
|
||||
class ShareCount(caching.base.CachingMixin, models.Model):
|
||||
class AddonShareCount(caching.base.CachingMixin, models.Model):
|
||||
addon = models.ForeignKey('addons.Addon')
|
||||
count = models.PositiveIntegerField()
|
||||
service = models.CharField(max_length=255, null=True)
|
||||
|
@ -101,7 +101,7 @@ class ShareCount(caching.base.CachingMixin, models.Model):
|
|||
db_table = 'stats_share_counts'
|
||||
|
||||
|
||||
class ShareCountTotal(caching.base.CachingMixin, models.Model):
|
||||
class AddonShareCountTotal(caching.base.CachingMixin, models.Model):
|
||||
addon = models.ForeignKey('addons.Addon')
|
||||
count = models.PositiveIntegerField()
|
||||
service = models.CharField(max_length=255, null=True)
|
||||
|
@ -113,6 +113,18 @@ class ShareCountTotal(caching.base.CachingMixin, models.Model):
|
|||
db_table = 'stats_share_counts_totals'
|
||||
|
||||
|
||||
# stats_collections_share_counts exists too, but we don't touch it.
|
||||
class CollectionShareCountTotal(caching.base.CachingMixin, models.Model):
|
||||
collection = models.ForeignKey('bandwagon.Collection')
|
||||
count = models.PositiveIntegerField()
|
||||
service = models.CharField(max_length=255, null=True)
|
||||
|
||||
objects = caching.base.CachingManager()
|
||||
|
||||
class Meta:
|
||||
db_table = 'stats_collections_share_counts_totals'
|
||||
|
||||
|
||||
class ContributionError(Exception):
|
||||
|
||||
def __init__(self, value):
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
DROP TABLE IF EXISTS `stats_collections_share_counts`;
|
||||
CREATE TABLE `stats_collections_share_counts` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`collection_id` int(11) unsigned NOT NULL DEFAULT '0',
|
||||
`count` int(11) unsigned NOT NULL DEFAULT '0',
|
||||
`service` varchar(128) DEFAULT NULL,
|
||||
`date` date NOT NULL DEFAULT '0000-00-00',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY (`collection_id`, `service`, `date`),
|
||||
CONSTRAINT FOREIGN KEY (collection_id) REFERENCES collections (id),
|
||||
KEY `date` (`date`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `stats_collections_share_counts_totals`;
|
||||
CREATE TABLE `stats_collections_share_counts_totals` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`collection_id` int(11) unsigned NOT NULL DEFAULT '0',
|
||||
`count` int(11) unsigned NOT NULL DEFAULT '0',
|
||||
`service` varchar(128) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY (`collection_id`, `service`),
|
||||
CONSTRAINT FOREIGN KEY (collection_id) REFERENCES collections (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
Загрузка…
Ссылка в новой задаче