~jcsackett/charmworld/bac-tag-constraints

« back to all changes in this revision

Viewing changes to charmworld/models.py

[r=sinzui][bug=1208477][author=abentley] Remove doctype attribute.

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
 
5
5
__metaclass__ = type
6
6
 
 
7
from bzrlib.branch import Branch
 
8
from bzrlib.errors import NoSuchRevision
7
9
from calendar import timegm
8
 
import functools
 
10
import copy
9
11
import hashlib
10
 
import itertools
11
12
import logging
12
13
from mimetypes import guess_type
13
14
import os
19
20
)
20
21
import random
21
22
import re
22
 
from  xml.etree import ElementTree
23
23
 
24
 
from bzrlib.branch import Branch
25
 
from bzrlib.errors import NoSuchRevision
26
24
from deployer import get_flattened_deployment
27
25
import gridfs
28
26
import magic
194
192
 
195
193
 
196
194
def construct_charm_id(charm_data, use_revision=True):
197
 
    # This creates an ID for a charm, but is not yet used to construct the _id
198
 
    # value for charm documents.
199
195
    elements = [charm_data['owner'], charm_data['series'], charm_data['name']]
200
196
    if use_revision:
201
197
        elements.append('%d' % charm_data['store_data']['revision'])
202
198
    return '~' + '/'.join(elements)
203
199
 
204
200
 
205
 
class FeaturedSource:
206
 
    """Charms and Bundles can be featured; that state is represented here."""
207
 
 
208
 
    def __init__(self, collection):
209
 
        # The collection that stores the featured state.
210
 
        self.collection = collection
211
 
 
212
 
    @classmethod
213
 
    def from_db(cls, db):
214
 
        """Create a source of charm feature data from a database."""
215
 
        return cls(db.featured)
216
 
 
217
 
    @staticmethod
218
 
    def _make_query(item_data, doctype):
219
 
        """Create a query that finds a featured item."""
220
 
        result = {
221
 
            'name': item_data['name'],
222
 
            'owner': item_data['owner'],
223
 
        }
224
 
        if doctype != 'bundle':
225
 
            # Bundles do not have a series.
226
 
            result['series'] = item_data['series']
227
 
        return result
228
 
 
229
 
    @classmethod
230
 
    def _make_document(cls, item_data, doctype):
231
 
        """Create a document that describes a featured item."""
232
 
        result = cls._make_query(item_data, doctype)
233
 
        result['doctype'] = doctype
234
 
        return result
235
 
 
236
 
    def set_featured(self, item_data, doctype):
237
 
        """Add an item from the featured collection."""
238
 
        featured_document = self._make_document(item_data, doctype)
239
 
        self.collection.update(
240
 
            featured_document, featured_document, upsert=True)
241
 
 
242
 
    def clear_featured(self, item_data, doctype):
243
 
        """Remove an item from the featured collection."""
244
 
        self.collection.remove(self._make_document(item_data, doctype))
245
 
 
246
 
    def is_featured(self, item_data, doctype):
247
 
        return bool(self.collection.find_one(
248
 
            self._make_document(item_data, doctype)))
249
 
 
250
 
    def _make_featured_query(self, doctype):
251
 
        """Construct a query that will search a collection for featured items.
252
 
        """
253
 
        ors = []
254
 
        for featured in self.collection.find({'doctype': doctype}):
255
 
            ors.append(self._make_query(featured, doctype))
256
 
        # If there are no featured items, then there can be no query to find
257
 
        # them.
258
 
        if not ors:
259
 
            return None
260
 
        return {'$or': ors}
261
 
 
262
 
    def get_featured(self, collection, doctype):
263
 
        # TODO Add support for fetching featured bundles when bundles become
264
 
        # featurable.
265
 
        assert doctype == 'charm', 'only charms can be featured at the moment'
266
 
        query = self._make_featured_query(doctype)
267
 
        # There is no query, so there are no featured items.
268
 
        if query is None:
269
 
            return []
270
 
        items = list(collection.find(self._make_featured_query(doctype)))
271
 
        # Order by identity (as defined by _make_document) and then revision
272
 
        # (this works because sort is stable).
273
 
        items.sort(key=lambda data: data['store_data']['revision'])
274
 
        identity = functools.partial(self._make_document, doctype=doctype)
275
 
        items.sort(key=identity)
276
 
        # Keep only the latest revision of each item.
277
 
        latest = [
278
 
            g.next() for k, g in itertools.groupby(items, key=identity)]
279
 
        return latest
280
 
 
281
 
 
282
201
class Charm:
283
202
    """A model to access a charm representation.
284
203
 
350
269
 
351
270
        # Provided by update_hash()
352
271
        'hash': None,
 
272
 
 
273
        # Provided by forms used by ~charmers.
 
274
        'is_featured': False,
353
275
    }
354
276
 
355
277
    OFFICIAL_CATEGORIES = [
742
664
        """
743
665
        return self._representation['short_url']
744
666
 
 
667
    @property
 
668
    def is_featured(self):
 
669
        """Is this charm featured in the interesting view?"""
 
670
        return self._representation['is_featured']
 
671
 
745
672
    def changes_since(self, date):
746
673
        """The changes that have been introduced since a given datetime."""
747
674
        date = timegm(date.timetuple())
916
843
            if magic.from_buffer(file_content, mime=True) != 'image/svg+xml':
917
844
                logger.warn("File is not an SVG: %s" % file_path)
918
845
                return
919
 
            # Check if the icon has viewBox set. If not, set it.
920
 
            root = ElementTree.fromstring(file_content)
921
 
            if root.get('viewBox') is None:
922
 
                width = root.get('width')
923
 
                height = root.get('height')
924
 
                if width is None or height is None:
925
 
                    logger.warn("SVG is malformed: "
926
 
                                + "width or height attributes not set")
927
 
                    return
928
 
                root.attrib['viewBox'] = "0 0 {0} {1}".format(width, height)
929
 
                file_content = ElementTree.tostring(root, encoding='UTF-8')
930
846
        charm_file.save(file_content)
931
847
        return charm_file
932
848
 
1296
1212
        return self.construct_id(self.owner, self.basket, self.name)
1297
1213
 
1298
1214
    @property
1299
 
    def short_url(self):
1300
 
        """Return the short URL for this bundle.
1301
 
 
1302
 
        The URL is the permanent path portion of the charmworld URL, e.g.
1303
 
        /bundle/~sinzui/mysql/5/tiny to support a full URL of
1304
 
        http://manage.jujucharms.com/bundle/~sinzui/mysql/5/tiny
1305
 
        """
1306
 
        return '/bundle/%s' % self.id
1307
 
 
1308
 
    @property
1309
1215
    def permanent_url(self):
1310
1216
        """The permanent url for the bundle."""
1311
1217
        return "jc:{}".format(self.id)
1419
1325
        index_client = ElasticSearchClient.from_settings(settings)
1420
1326
 
1421
1327
    # Augment the deployer_config with additional information for indexing.
1422
 
    index_data = {}
 
1328
    ids = {}
 
1329
    config_copy = copy.deepcopy(deployer_config)
 
1330
    for key in config_copy:
 
1331
        ids[key] = Bundle.construct_id(owner, basket_id, key)
 
1332
        config_copy[key]['id'] = ids[key]
 
1333
        config_copy[key]['owner'] = owner
 
1334
        config_copy[key]['name'] = key
 
1335
        config_copy[key]['basket'] = basket_id
 
1336
 
 
1337
    index_client.index_bundles(config_copy.values())
 
1338
    if collection is None:
 
1339
        return
1423
1340
    for key in deployer_config:
1424
1341
        data = get_flattened_deployment(deployer_config, key)
1425
 
        _id = Bundle.construct_id(owner, basket_id, key)
1426
1342
        bundle_doc = {
1427
 
            '_id': _id,
 
1343
            '_id': ids[key],
1428
1344
            'name': key,
1429
1345
            'owner': owner,
1430
1346
            'basket': basket_id,
1431
1347
            'data': data,
1432
1348
        }
1433
 
        # Set up indexing.
1434
 
        index_data[key] = bundle_doc
1435
 
 
1436
 
        if collection is not None:
1437
 
            collection.save(bundle_doc)
1438
 
 
1439
 
    index_client.index_bundles(index_data.values())
 
1349
        collection.save(bundle_doc)
1440
1350
 
1441
1351
 
1442
1352
def slurp_files(fs, tree, entry_source=None):