def page_index(request): db = app.get_feature('document_storage').default_db pages = Page.objects(db).order_by('date_time') for p in pages: print p.pk, p, p.summary, p.image return {'pages': pages, 'thumbnail': _get_thumbnail}
def ocr(args): """Performs optical character recognition (OCR) on given image. The image can be defined either as path to a file or as the primary key of a Page object. The latter case also enables saving the resulting text to the object. By default the result is simply printed. """ try: assert args.pk or args.path, '--path or --pk must be provided' assert not (args.pk and args.path), 'specify either --path or --pk' assert not (args.path and args.save), 'use --save only with --pk' except AssertionError as e: raise CommandError(e) if args.pk: db = app.get_feature('document_storage').default_db page = Page.object(db, args.pk) path = page.image.full_path else: path = args.path text = image_to_text(path, language=args.language) if args.save: page.details = text page.save() yield 'Page saved with the new text.' else: yield text
def add_need(args): """Creates a need with given text. Project name can be incomplete but unambiguous. """ for func in flatten_nargs_plus, fix_unicode: func(args, 'project', 'summary') db = app.get_feature('document_storage').default_db # FIXME Docu seems to fail at filling default values such as "is_satisfied" need = Need(summary=args.summary, is_satisfied=False) project = None if args.project: qs = SystemUnderDevelopment.objects(db) qs = qs.where(summary__matches_caseless=args.project) if not qs: yield('No projects matching "{0}"'.format(args.project)) return if 1 < len(qs): yield('Found {0} projects matching "{1}". Which to use?'.format( len(qs), args.project)) for candidate in qs: yield(u'- {summary}'.format(**candidate)) return project = qs[0] # TODO: check for duplicates; require --force to add with dupes pk = need.save(db) # redundant? yield(u'Added need: {0}'.format(need.summary)) if project: project.needs.append(need.pk) project.save() yield(u' to project: {0}.'.format(project.summary)) yield(' primary key: {0}.'.format(pk))
def __call__(self, environ, start_response): plugin = app.get_feature(Routing.features) # create request object request = Request(environ) logger.debug('Got new request object') local.request = request request_ready.send(sender=request, request=request) # bind URLs plugin.env['urls'] = plugin.env['url_map'].bind_to_environ(environ) #urls_bound.send(sender=self, map_adapter=self.urls) # determine current URL, find and call corresponding view function # another approach: http://stackoverflow.com/questions/1796063/werkzeug-mapping-urls-to-views-via-endpoint logger.debug('Dispatching the request') result = plugin.env['urls'].dispatch(plugin._find_and_call_view, catch_http_exceptions=True) #except NotFound: # return self.app()(environ, start_response) return result(environ, start_response)
def get_feature(name): """ Returns the extension class instance """ try: return app.get_feature(name) except KeyError: raise KeyError('Plugin with identity "{0}" does not exist. Configured ' 'plugins are: {1}'.format(identity, app._features.keys()))
def import_dump(path): "Imports an XML dump of all your bookmarks on delicious.com" assert os.path.exists(path) db = app.get_feature("document_storage").default_db x = xml.dom.minidom.parse(path) posts = x.documentElement.getElementsByTagName("post") seen_cnt = saved_cnt = 0 for post in posts: seen_cnt += 1 data = dict(post.attributes.items()) if Bookmark.objects(db).where(url=data["href"]).count(): continue bookmark = Bookmark( url=data["href"], summary=data["description"], details=data["extended"], tags=data["tag"].split(), date_time=datetime.datetime.strptime(data["time"], "%Y-%m-%dT%H:%M:%SZ"), shared=False if data.get("shared") == "no" else True, ) # FIXME: check uniqueness print u"Saving {date_time} {url}...".format(**bookmark) pk = bookmark.save(db) saved_cnt += 1 print "Saved {saved_cnt} bookmarks of {seen_cnt}.".format(**locals())
def _import_one_sms(data, current_number=None, dry_run=False): # define who's the actor and who's the receiver other_number = unicode(data['Number']) if data['State'] == 'Sent': sent_by, sent_to = current_number, other_number else: sent_by, sent_to = other_number, current_number fields = dict( sent_by = sent_by, sent_to = sent_to, date_time = data['DateTime'], #is_confirmed = True, # it is sent, yup summary = data['Text'] or u'[text missing]', # u'' is invalid, None not accepted ) search_fields = dict(fields) search_fields.pop('summary') # workaround: we don't know which number was "current" when the message was # last imported, so we look for any "our" number. my_numbers = _get_my_numbers() k = 'sent_by' if data['State']=='Sent' else 'sent_to' search_fields.pop(k) search_fields.update({'{0}__in'.format(k): my_numbers.values()}) db = app.get_feature('document_storage').default_db if not GammuSMS.objects(db).where(**search_fields).count(): #print 'NOT FOUND:', search_fields print u'SAVING {0} {1} → {2} {3}{4}'.format( fields['date_time'], sent_by, sent_to, fields['summary'][:20], u'…' if 20 < len(fields['summary']) else '') if dry_run: return '(stub: dry run)' else: return GammuSMS(**fields).save(db)
def redirect_to(endpoint, **kwargs): """ A wrapper for :func:`redirect`. The difference is that the endpoint is first resolved via :func:`url_for` and then passed to :func:`redirect`. """ plugin = app.get_feature(BaseRoutingPlugin.features) return plugin.redirect_to(endpoint, **kwargs)
def list_events(query=None, format=u'{date_time} {summary}', date=None): "Displays a list of matching events." db = app.get_feature('document_storage').default_db events = Event.objects(db) if query: events = events.where(summary__matches_caseless=query) for event in events: yield format.format(**event)
def make_env(self): # XXX it is assumed that Jinja2 is the templating engine. It should be # possible to populate other engines' globals. logger.debug('Contributing functions to the template environment...') templating = app.get_feature('templating') templating.update_template_context(dict( get_breadcrumb_title = get_title, get_breadcrumb_trail = get_trail_titles, ))
def render_template(template_path, **extra_context): """ Renders given template file with given context. :template_path: path to the template; must belong to one of directories listed in `searchpaths` (see configuration). """ plugin = app.get_feature(FEATURE) return plugin.render_template(template_path, extra_context)
def _get_my_numbers(): bundle_name = '.'.join(__name__.split('.')[:-1]) my_numbers = app.get_feature('mobile').get('my_numbers') assert my_numbers, ('Gammu integration requires "my_numbers" setting. ' 'It must be a dictionary like {"work": "123"}.') assert isinstance(my_numbers, dict) def _normalize(value): if isinstance(value, int): return u'+{0}'.format(value) return unicode(value) return dict((k,_normalize(v)) for k,v in my_numbers.iteritems())
def url_for(endpoint, **kwargs): """ Given the endpoint and keyword arguments, builds and returns the URL. :param endpoint: either a callable object, or a string representing the dotted path to such object (e.g. ``myapp.views.list_entries``) The keywords are passed to :meth:`MapAdapter.build`. """ plugin = app.get_feature(BaseRoutingPlugin.features) return plugin.url_for(endpoint, **kwargs)
def add_pages(args): _args_to_unicode(args, ['language', 'summary', 'summary_prefix']) db = app.get_feature('document_storage').default_db # check if the files exist for path in args.paths: assert os.path.exists(path) # import for path in args.paths: yield '* importing {0} (language {1})'.format(path, args.language) fingerprint = get_file_hash(open(path, 'rb')) # check file hash uniqueness if Page.objects(db).where(source_fingerprint=fingerprint): yield '...already in the database.' continue p = Page() p.summary = args.summary or get_summary_from_path(path) if args.summary_prefix: p.summary = u'{0} {1}'.format(args.summary_prefix, p.summary) p.language = args.language or None p.source_fingerprint = fingerprint if not args.no_ocr: try: p.details = image_to_text(path=path, language=p.language) except RuntimeError as e: if not args.skip_ocr_errors: raise CommandError(e) yield '(OCR failed, saving only image itself)' # usually we don't need heavy formats like ppm or tiff even for OCR img = Image.open(path) if args.format: fmt = args.format elif img.format not in IMAGE_FORMATS: fmt = IMAGE_FORMATS[0] else: fmt = img.format img.save(TMP_FILENAME, fmt) p['image'] = open(TMP_FILENAME, 'rb') # provide original path so that the resulting filename is inherited p['image'].path = path p.save(db)
def import_contacts(): db = app.get_feature('document_storage').default_db sm = _get_state_machine() memory = 'ME' # TODO: allow importing from SIM memory, too # NOTE: GetNextMemory is not implemented as of python-gammu 1.28.0, so we # cannot reuse the iteration code from _iterate_results. # # Also, we have to iterate the whole lot of slots despite there can be # actually a very few records at the very beginning of the list. The import # process may seem too long because of this. seen_cnt = saved_cnt = 0 location = 0 while True: location += 1 try: item = sm.GetMemory(Type=memory, Location=location) except gammu.ERR_EMPTY: # empty records are not always at the end continue except gammu.ERR_INVALIDLOCATION: # aha, looks like there are no more slots break else: seen_cnt += 1 elems = [(x['Type'], x['Value']) for x in item['Entries']] person, contacts = GammuContact.from_raw_elems(elems) def _contact_exists(contact): conditions = {'kind': contact.kind, 'value': contact.value} duplicates = GammuContact.objects(db).where(**conditions) return bool(duplicates.count()) contacts = [c for c in contacts if not _contact_exists(c)] if not contacts: # even the Person instance is not saved if there's no new # contact information (the contacts could be moved btw) continue # this could be improved so that details don't matter, etc. person, created = db.get_or_create(type(person), **person) for contact in contacts: contact.person = person contact.save(db) print 'Imported {saved_cnt} of {seen_cnt}.'.format(**locals())
def mark_need(args): "Marks matching needs as satisfied." for func in flatten_nargs_plus, fix_unicode: func(args, 'query', 'project') assert not (args.important and args.unimportant) assert not (args.satisfied and args.unsatisfied) assert any([args.satisfied, args.unsatisfied, args.important, args.unimportant]), 'A flag must be chosen' if args.primary_key: assert not (args.query or args.project), ( '--primary-key cannot be combined with --query/--project') db = app.get_feature('document_storage').default_db needs = [db.get(Need, args.primary_key)] else: needs = ensure_results(find_needs, project=args.project, query=args.query) for need in needs: satisfied = 'satisfied' if need.is_satisfied else 'unsatisfied' important = 'important' if not need.is_discarded else 'unimportant' yield(u'- {summary} ({satisfied}, {important})'.format( satisfied=satisfied, important=important, **need)) yield('') if confirm('Apply changes to these items', default=True, skip=args.yes): if args.dry_run: yield('Simulation: nothing was actually changed in the database.') else: for need in needs: yield(u'Marking "{summary}"...'.format(**need)) if args.satisfied and not need.is_satisfied: yield(u' + unsatisfied → satisfied') need.is_satisfied = True if args.unsatisfied and need.is_satisfied: yield(u' + satisfied → unsatisfied') need.is_satisfied = False if args.important and need.is_discarded: need.is_discarded = False yield(u' + discarded → important') if args.unimportant and not need.is_discarded: yield(u' + important → discarded') need.is_discarded = True need.save() yield('Changes have been applied.') else: yield('Operation cancelled.')
def rename_need(args): """Renames matching item. Requires exactly one match. If `summary` is in the form "/foo/bar/", it is interpreted as a regular expression. Usage:: $ needs rename -q "teh stuff" the stuff $ needs rename -q "teh stuff" /teh/the/ """ for func in flatten_nargs_plus, fix_unicode: func(args, 'query', 'project', 'to') summary = args.to.strip() if args.to else None assert summary if args.primary_key: db = app.get_feature('document_storage').default_db need = db.get(Need, args.primary_key) else: try: # TODO: support batch renames (with regex only?) need = get_single(find_needs, query=args.query) except (NotFound, MultipleMatches) as e: yield(u'Bad query "{0}": {1}'.format(args.query, e)) # if new value looks like a regex, then use it that way match = re.match('^/([^/]+?)/([^/]+?)/$', summary) if match: old, new = match.groups() yield(u'Using regex: replacing "{old}" with "{new}"'.format(**locals())) summary = re.sub(old, new, need.summary) if need.summary == summary: yield(u'Nothing changed.') else: yield(u'Renaming "{0}" to "{1}"'.format(need.summary, summary)) if args.dry_run: yield('Simulation: nothing was actually changed in the database.') else: need.summary = summary need.save()
def view_need(args): fix_unicode(args, 'project', 'query') if (not (args.primary_key or args.query) or (args.primary_key and args.query)): raise CommandError('Please specify either --query or --primary-key') need = None try: project = get_single(find_projects, query=args.project) except MultipleMatches: project = None if args.query: try: need = get_single(find_needs, query=args.query, project=project) except MultipleMatches as e: yield(str(e)) raise NotImplementedError # TODO if args.primary_key: db = app.get_feature('document_storage').default_db need = db.get(Need, args.primary_key) if need: yield(need.dump())
def get_title(path=None): """ Resolves the URL to a view function and returns the relevant title. The title is either taken from the view function's attribute ``breadcrumb`` (can be set with :func:`entitled` or manually) or from the view function's ``__name__``. :param path: The "path" part of a URL (see werkzeug.Request.path). If `None`, current URL is used (from context locals). """ routing = app.get_feature('routing') assert routing.env['urls'], 'Application must be initialized' if path is None or path == '': path = request.path assert isinstance(path, basestring) try: func, kwargs = routing.env['urls'].match(path) except werkzeug.exceptions.NotFound: return u'(ENDPOINT NOT FOUND)' except werkzeug.routing.RequestRedirect as e: return u'({0})'.format(e.new_url) except werkzeug.exceptions.MethodNotAllowed as e: # if GET is inadequate for this URL, no need to give it a title # XXX ...or not? what if it the title is going to be used for the # heading of a page that accepts a non-GET method but then normally # displays a form or some confirmation messages? return u'(Cannot access this URL with GET)' else: if hasattr(func, BREADCRUMB_ATTR_NAME): name_source = getattr(func, BREADCRUMB_ATTR_NAME) if hasattr(name_source, '__call__'): return name_source(**kwargs) else: return unicode(name_source) return unicode(func.__name__.replace('_', ' '))
def delete_need(args): """Deletes needs with given primary key or with summary matching given query. If the query matches more than one item, the operation is cancelled. """ for func in flatten_nargs_plus, fix_unicode: func(args, 'query', 'project') if args.primary_key: yield('Deleting need with primary key {0}'.format(args.primary_key)) db = app.get_feature('document_storage').default_db need = db.get(Need, args.primary_key) if confirm(u'Delete need {summary}'.format(**need)): if not args.dry_run: remove_need(need, dry_run=args.dry_run) else: yield('Operation cancelled.') elif args.query or args.project: try: needs = ensure_results(find_needs, project=args.project, query=args.query) except NotFound as e: raise CommandError('Cannot delete items: {0}'.format(e)) yield('Matching needs:') for need in needs: yield(u'- {summary}'.format(**need)) yield(' primary key: {0}'.format(need.pk)) if confirm('Delete these items'): if not args.dry_run: for need in needs: yield('Dropping {summary}…'.format(**need)) remove_need(need, dry_run=args.dry_run) else: yield('Operation cancelled.') else: yield('Please specify either --primary-key or --query/--project.') if args.dry_run: yield('Simulation: nothing was actually changed in the database.')
def __getitem__(self, label): plugin = app.get_feature(FEATURE) if label in plugin.env: return plugin.env[label] raise KeyError('Unknown document storage "{0}". These storages are ' 'configured: {1}'.format(label, plugin.env.keys()))
def import_sms(args): """Imports SMS from mobile phone. :param current_phone: Expects "current phone" so that incoming and outgoing messages can be correctly populated with both sender and receiver numbers (the phone only stores the "other" number) and you have to manually specify yours). Can be omitted if there's only one "my number" in the settings. Note that you should specify the label (e.g. "personal", "work" or "primary") instead of the number itself. The labels are defined in the "my_numbers" setting for the bundle:: extensions: orgtool.ext.mobile.MobileETL: my_numbers: home: +1234567890 work: +0987654321 :param full_archive: If True, attempts to import all messages in the phone. Despite this implies checking for duplicates, the process takes longer and issues with dates and phone numbers may arise. By default this option is off and only "new" messages are imported. Message is considered "new" if its date is greater than the last known message's date. Time is ignored in this check. :param dry_run: If True, newly imported messages are not saved. Use this for testing. Default is False. """ if args.dry_run: yield 'Dry run, no data will be changed.' db = app.get_feature('document_storage').default_db my_numbers = _get_my_numbers() current_number = None if args.current_phone: assert args.current_phone in my_numbers, ( 'unknown number label "{0}"'.format(args.current_phone)) current_number = my_numbers[args.current_phone] else: if len(my_numbers) != 1: raise CommandError('Which phone (SIM card) is that? Choices: ' '{0}'.format(', '.join(my_numbers))) the_only_label = my_numbers.keys()[0] current_number = my_numbers[the_only_label] assert current_number # find latest known message date if args.full_archive: last_imported_date = None yield 'Importing all messages from the phone...' else: msgs = GammuSMS.objects(db).order_by('date_time', reverse=True) last_imported_date = msgs[0].date_time.date() if msgs.count() else None yield 'Importing messages since {0}...'.format(last_imported_date) sm = _get_state_machine() seen_cnt = saved_cnt = 0 for data in _iterate_results(sm.GetNextSMS, Folder=0): if last_imported_date and not args.full_archive: # skip message without full check if a later message had been # already imported if data['DateTime'].date() < last_imported_date: continue saved = _import_one_sms(data, current_number, dry_run=args.dry_run) if saved: saved_cnt += 1 seen_cnt += 1 yield 'Imported {saved_cnt} of {seen_cnt}.'.format(**locals()) if args.dry_run: yield '(Dry run, nothing really changed.)'
def get_default_currency(): ext = app.get_feature('money') return unicode(ext.env['default_currency'])
def page_detail(request, pk): db = app.get_feature('document_storage').default_db page = db.get(Page, pk) return {'page': page, 'thumbnail': _get_thumbnail}
def register_templates(module_path, dir_name=DEFAULT_PATH, prefix=None): """See :meth:`JinjaPlugin.register_templates` and :meth:`MakoPlugin.register_templates`. """ app.get_feature(FEATURE).register_templates(module_path, dir_name, prefix)
BASE_IMAGE_URL = u'/media/scanned_papers/' @url('/') @entitled(u'Pages') @as_html('papers/page_index.html') def page_index(request): db = app.get_feature('document_storage').default_db pages = Page.objects(db).order_by('date_time') for p in pages: print p.pk, p, p.summary, p.image return {'pages': pages, 'thumbnail': _get_thumbnail} @url('/<string:pk>') @entitled(lambda pk: u'{0}'.format( app.get_feature('document_storage').default_db.get(Page,pk))) @as_html('papers/page_detail.html') def page_detail(request, pk): db = app.get_feature('document_storage').default_db page = db.get(Page, pk) return {'page': page, 'thumbnail': _get_thumbnail} def _get_thumbnail(image, w=100, h=100): import Image import os appendix = '.thumbnail_{0}x{1}.{2}'.format(w, h, 'JPEG')# image.file.format) th_full_path = image.full_path + appendix if not os.path.exists(th_full_path): print 'NOT CACHED'
def get_image_base_path(): from tool import app ext = app.get_feature('papers') return ext.env['image_base_path']
def init_func(): ns = {'app': app} # FIXME this should be conditional db_ext = app.get_feature('document_storage') ns.update(db=db_ext.default_db) return ns
def env(name): "Returns plugin environment variable of given name." plugin = app.get_feature('admin') return plugin.env[name]