diff --git a/ckan/config/middleware/flask_app.py b/ckan/config/middleware/flask_app.py index a00470b050a..3593ff1c937 100644 --- a/ckan/config/middleware/flask_app.py +++ b/ckan/config/middleware/flask_app.py @@ -188,7 +188,11 @@ def hello_world_post(): # Set up each IBlueprint extension as a Flask Blueprint for plugin in PluginImplementations(IBlueprint): if hasattr(plugin, 'get_blueprint'): - app.register_extension_blueprint(plugin.get_blueprint()) + plugin_blueprints = plugin.get_blueprint() + if not isinstance(plugin_blueprints, list): + plugin_blueprints = [plugin_blueprints] + for blueprint in plugin_blueprints: + app.register_extension_blueprint(blueprint) lib_plugins.register_package_blueprints(app) lib_plugins.register_group_blueprints(app) diff --git a/ckan/config/routing.py b/ckan/config/routing.py index 6ead544ef83..fb771ea76be 100644 --- a/ckan/config/routing.py +++ b/ckan/config/routing.py @@ -112,19 +112,6 @@ def make_map(): if not hasattr(route, '_ckan_core'): route._ckan_core = False - # CKAN API versioned. - register_list = [ - 'package', 'dataset', 'resource', 'group', 'revision', - 'licenses', 'rating', 'user', 'activity' - ] - register_list_str = '|'.join(register_list) - - # /api ver 1, 2, 3 or none - with SubMapper( - map, controller='api', path_prefix='/api{ver:/1|/2|/3|}', - ver='/1') as m: - m.connect('/search/{register}', action='search') - # /api/util ver 1, 2 or none with SubMapper( map, controller='api', path_prefix='/api{ver:/1|/2|}', @@ -158,7 +145,6 @@ def make_map(): m.connect('/i18n/strings_{lang}.js', action='i18n_js_strings') m.connect('/util/redirect', action='redirect') m.connect('/testing/primer', action='primer') - m.connect('/testing/markup', action='markup') # robots.txt map.connect('/(robots.txt)', controller='template', action='view') diff --git a/ckan/controllers/api.py b/ckan/controllers/api.py index 0093aed72e8..a831e075958 100644 --- a/ckan/controllers/api.py +++ b/ckan/controllers/api.py @@ -257,143 +257,6 @@ def action(self, logic_function, ver=None): return self._finish(500, return_dict, content_type='json') return self._finish_ok(return_dict) - def _get_action_from_map(self, action_map, register, subregister): - ''' Helper function to get the action function specified in - the action map''' - - # translate old package calls to use dataset - if register == 'package': - register = 'dataset' - - action = action_map.get((register, subregister)) - if not action: - action = action_map.get(register) - if action: - return get_action(action) - - def search(self, ver=None, register=None): - - log.debug('search %s params: %r', register, request.params) - if register == 'revision': - since_time = None - if 'since_id' in request.params: - id = request.params['since_id'] - if not id: - return self._finish_bad_request( - _(u'No revision specified')) - rev = model.Session.query(model.Revision).get(id) - if rev is None: - return self._finish_not_found( - _(u'There is no revision with id: %s') % id) - since_time = rev.timestamp - elif 'since_time' in request.params: - since_time_str = request.params['since_time'] - try: - since_time = h.date_str_to_datetime(since_time_str) - except ValueError as inst: - return self._finish_bad_request('ValueError: %s' % inst) - else: - return self._finish_bad_request( - _("Missing search term ('since_id=UUID' or " + - " 'since_time=TIMESTAMP')")) - revs = model.Session.query(model.Revision) \ - .filter(model.Revision.timestamp > since_time) \ - .order_by(model.Revision.timestamp) \ - .limit(50) # reasonable enough for a page - return self._finish_ok([rev.id for rev in revs]) - elif register in ['dataset', 'package', 'resource']: - try: - params = MultiDict(self._get_search_params(request.params)) - except ValueError as e: - return self._finish_bad_request( - _('Could not read parameters: %r' % e)) - - # if using API v2, default to returning the package ID if - # no field list is specified - if register in ['dataset', 'package'] and not params.get('fl'): - params['fl'] = 'id' if ver == 2 else 'name' - - try: - if register == 'resource': - query = search.query_for(model.Resource) - - # resource search still uses ckan query parser - options = search.QueryOptions() - for k, v in params.items(): - if (k in search.DEFAULT_OPTIONS.keys()): - options[k] = v - options.update(params) - options.username = c.user - options.search_tags = False - options.return_objects = False - query_fields = MultiDict() - for field, value in params.items(): - field = field.strip() - if field in search.DEFAULT_OPTIONS.keys() or \ - field in IGNORE_FIELDS: - continue - values = [value] - if isinstance(value, list): - values = value - for v in values: - query_fields.add(field, v) - - results = query.run( - query=params.get('q'), - fields=query_fields, - options=options - ) - else: - # For package searches in API v3 and higher, we can pass - # parameters straight to Solr. - if ver in [1, 2]: - # Otherwise, put all unrecognised ones into the q - # parameter - params = search.\ - convert_legacy_parameters_to_solr(params) - query = search.query_for(model.Package) - - # Remove any existing fq param and set the capacity to - # public - if 'fq' in params: - del params['fq'] - params['fq'] = '+capacity:public' - # if callback is specified we do not want to send that to - # the search - if 'callback' in params: - del params['callback'] - results = query.run(params) - return self._finish_ok(results) - except search.SearchError as e: - log.exception(e) - return self._finish_bad_request( - _('Bad search option: %s') % e) - else: - return self._finish_not_found( - _('Unknown register: %s') % register) - - @classmethod - def _get_search_params(cls, request_params): - if 'qjson' in request_params: - try: - qjson_param = request_params['qjson'].replace('\\\\u', '\\u') - params = h.json.loads(qjson_param, encoding='utf8') - except ValueError as e: - raise ValueError(_('Malformed qjson value: %r') - % e) - elif len(request_params) == 1 and \ - len(request_params.values()[0]) < 2 and \ - request_params.keys()[0].startswith('{'): - # e.g. {some-json}='1' or {some-json}='' - params = h.json.loads(request_params.keys()[0], encoding='utf8') - else: - params = request_params - if not isinstance(params, (UnicodeMultiDict, dict)): - msg = _('Request params must be in form ' + - 'of a json encoded dictionary.') - raise ValueError(msg) - return params - @jsonp.jsonpify def user_autocomplete(self): q = request.params.get('q', '') @@ -444,59 +307,6 @@ def organization_autocomplete(self): get_action('organization_autocomplete')(context, data_dict) return organization_list - def dataset_autocomplete(self): - q = request.params.get('incomplete', '') - limit = request.params.get('limit', 10) - package_dicts = [] - if q: - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - - data_dict = {'q': q, 'limit': limit} - - package_dicts = get_action('package_autocomplete')(context, - data_dict) - - resultSet = {'ResultSet': {'Result': package_dicts}} - return self._finish_ok(resultSet) - - def tag_autocomplete(self): - q = request.str_params.get('incomplete', '') - q = text_type(urllib.unquote(q), 'utf-8') - limit = request.params.get('limit', 10) - tag_names = [] - if q: - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - - data_dict = {'q': q, 'limit': limit} - - tag_names = get_action('tag_autocomplete')(context, data_dict) - - resultSet = { - 'ResultSet': { - 'Result': [{'Name': tag} for tag in tag_names] - } - } - return self._finish_ok(resultSet) - - def format_autocomplete(self): - q = request.params.get('incomplete', '') - limit = request.params.get('limit', 5) - formats = [] - if q: - context = {'model': model, 'session': model.Session, - 'user': c.user, 'auth_user_obj': c.userobj} - data_dict = {'q': q, 'limit': limit} - formats = get_action('format_autocomplete')(context, data_dict) - - resultSet = { - 'ResultSet': { - 'Result': [{'Format': format} for format in formats] - } - } - return self._finish_ok(resultSet) - def munge_package_name(self): name = request.params.get('name') munged_name = munge.munge_name(name) diff --git a/ckan/controllers/util.py b/ckan/controllers/util.py index 4a41bfb0dc2..8242222e97e 100644 --- a/ckan/controllers/util.py +++ b/ckan/controllers/util.py @@ -27,11 +27,6 @@ def primer(self): This is useful for development/styling of ckan. ''' return base.render('development/primer.html') - def markup(self): - ''' Render all html elements out onto a single page. - This is useful for development/styling of ckan. ''' - return base.render('development/markup.html') - def i18_js_strings(self, lang): ''' This is used to produce the translations for javascript. ''' i18n.set_lang(lang) diff --git a/ckan/logic/auth/create.py b/ckan/logic/auth/create.py index 70480abcd94..7d0aaf4fe80 100644 --- a/ckan/logic/auth/create.py +++ b/ckan/logic/auth/create.py @@ -149,10 +149,12 @@ def user_create(context, data_dict=None): 'create users')} return {'success': True} + def user_invite(context, data_dict): data_dict['id'] = data_dict['group_id'] return group_member_create(context, data_dict) + def _check_group_auth(context, data_dict): '''Has this user got update permission for all of the given groups? If there is a package in the context then ignore that package's groups. @@ -205,14 +207,17 @@ def vocabulary_create(context, data_dict): # sysadmins only return {'success': False} + def activity_create(context, data_dict): # sysadmins only return {'success': False} + def tag_create(context, data_dict): # sysadmins only return {'success': False} + def _group_or_org_member_create(context, data_dict): user = context['user'] group_id = data_dict['id'] @@ -220,12 +225,15 @@ def _group_or_org_member_create(context, data_dict): return {'success': False, 'msg': _('User %s not authorized to add members') % user} return {'success': True} + def organization_member_create(context, data_dict): return _group_or_org_member_create(context, data_dict) + def group_member_create(context, data_dict): return _group_or_org_member_create(context, data_dict) + def member_create(context, data_dict): group = logic_auth.get_group_object(context, data_dict) user = context['user'] diff --git a/ckan/templates-bs2/development/markup.html b/ckan/templates-bs2/development/markup.html deleted file mode 100644 index 9c4fcffce30..00000000000 --- a/ckan/templates-bs2/development/markup.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'base.html' %} - -{% block page %} -
-
- {% snippet 'development/snippets/markup.html' %} -
-
-{% endblock %} diff --git a/ckan/templates-bs2/development/snippets/markup.html b/ckan/templates-bs2/development/snippets/markup.html deleted file mode 100644 index fadb4936db2..00000000000 --- a/ckan/templates-bs2/development/snippets/markup.html +++ /dev/null @@ -1,416 +0,0 @@ -

General Prose

-
-

Sections Linked

-

The main page header of this guide is an h1 element. Any header elements may include links, as depicted in the example.

-

The secondary header above is an h2 element, which may be used for any form of important page-level header. More than one may be used per page. Consider using an h2 unless you need a header level of less importance, or as a sub-header to an existing h2 element.

-

Third-Level Header Linked

-

The header above is an h3 element, which may be used for any form of page-level header which falls below the h2 header in a document hierarchy.

-

Fourth-Level Header Linked

-

The header above is an h4 element, which may be used for any form of page-level header which falls below the h3 header in a document hierarchy.

-
Fifth-Level Header Linked
-

The header above is an h5 element, which may be used for any form of page-level header which falls below the h4 header in a document hierarchy.

-
Sixth-Level Header Linked
-

The header above is an h6 element, which may be used for any form of page-level header which falls below the h5 header in a document hierarchy.

- -

Grouping content

-

Paragraphs

-

All paragraphs are wrapped in p tags. Additionally, p elements can be wrapped with a blockquote element if the p element is indeed a quote. Historically, blockquote has been used purely to force indents, but this is now achieved using CSS. Reserve blockquote for quotes.

- -

Horizontal rule

-

The hr element represents a paragraph-level thematic break, e.g. a scene change in a story, or a transition to another topic within a section of a reference book. The following extract from Pandora’s Star by Peter F. Hamilton shows two paragraphs that precede a scene change and the paragraph that follows it:

-
-

Dudley was ninety-two, in his second life, and fast approaching time for another rejuvenation. Despite his body having the physical age of a standard fifty-year-old, the prospect of a long degrading campaign within academia was one he regarded with dread. For a supposedly advanced civilization, the Intersolar Commonwearth could be appallingly backward at times, not to mention cruel.

-

Maybe it won’t be that bad, he told himself. The lie was comforting enough to get him through the rest of the night’s shift.

-
-

The Carlton AllLander drove Dudley home just after dawn. Like the astronomer, the vehicle was old and worn, but perfectly capable of doing its job. It had a cheap diesel engine, common enough on a semi-frontier world like Gralmond, although its drive array was a thoroughly modern photoneural processor. With its high suspension and deep-tread tyres it could plough along the dirt track to the observatory in all weather and seasons, including the metre-deep snow of Gralmond’s winters.

-
- -

Pre-formatted text

-

The pre element represents a block of pre-formatted text, in which structure is represented by typographic conventions rather than by elements. Such examples are an e-mail (with paragraphs indicated by blank lines, lists indicated by lines prefixed with a bullet), fragments of computer code (with structure indicated according to the conventions of that language) or displaying ASCII art. Here’s an example showing the printable characters of ASCII:

-
-
  ! " # $ % & ' ( ) * + , - . /
-0 1 2 3 4 5 6 7 8 9 : ; < = > ?
-@ A B C D E F G H I J K L M N O
-P Q R S T U V W X Y Z [ \ ] ^ _
-` a b c d e f g h i j k l m n o
-p q r s t u v w x y z { | } ~ 
-
- -

Blockquotes

-

The blockquote element represents a section that is being quoted from another source.

-
-
-

Many forms of Government have been tried, and will be tried in this world of sin and woe. No one pretends that democracy is perfect or all-wise. Indeed, it has been said that democracy is the worst form of government except all those other forms that have been tried from time to time.

-
-

Winston Churchill, in a speech to the House of Commons. 11th November 1947

-
-

Additionally, you might wish to cite the source, as in the above example. The correct method involves including the cite attribute on the blockquote element, but since no browser makes any use of that information, it’s useful to link to the source also.

- -

Ordered list

-

The ol element denotes an ordered list, and various numbering schemes are available through the CSS (including 1,2,3… a,b,c… i,ii,iii… and so on). Each item requires a surrounding <li> and </li> tag, to denote individual items within the list (as you may have guessed, li stands for list item).

-
-
    -
  1. This is an ordered list.
  2. -
  3. - This is the second item, which contains a sub list -
      -
    1. This is the sub list, which is also ordered.
    2. -
    3. It has two items.
    4. -
    -
  4. -
  5. This is the final item on this list.
  6. -
-
- -

Unordered list

-

The ul element denotes an unordered list (ie. a list of loose items that don’t require numbering, or a bulleted list). Again, each item requires a surrounding <li> and </li> tag, to denote individual items. Here is an example list showing the constituent parts of the British Isles:

-
- -
-

Sometimes we may want each list item to contain block elements, typically a paragraph or two.

-
- -
- -

Definition list

-

The dl element is for another type of list called a definition list. Instead of list items, the content of a dl consists of dt (Definition Term) and dd (Definition description) pairs. Though it may be called a “definition list”, dl can apply to other scenarios where a parent/child relationship is applicable. For example, it may be used for marking up dialogues, with each dt naming a speaker, and each dd containing his or her words.

-
-
-
This is a term.
-
This is the definition of that term, which both live in a dl.
-
Here is another term.
-
And it gets a definition too, which is this line.
-
Here is term that shares a definition with the term below.
-
Here is a defined term.
-
dt terms may stand on their own without an accompanying dd, but in that case they share descriptions with the next available dt. You may not have a dd without a parent dt.
-
-
- -

Figures

-

Figures are usually used to refer to images:

-
-
- Example image -
-

This is a placeholder image, with supporting caption.

-
-
-
-

Here, a part of a poem is marked up using figure:

-
-
-

‘Twas brillig, and the slithy toves
- Did gyre and gimble in the wabe;
- All mimsy were the borogoves,
- And the mome raths outgrabe.

-
-

Jabberwocky (first verse). Lewis Carroll, 1832-98

-
-
-
- -

Text-level Semantics

-

There are a number of inline HTML elements you may use anywhere within other elements.

- -

Links and anchors

-

The a element is used to hyperlink text, be that to another page, a named fragment on the current page or any other location on the web. Example:

-
-

Go to the home page or return to the top of this page.

-
- -

Stressed emphasis

-

The em element is used to denote text with stressed emphasis, i.e., something you’d pronounce differently. Where italicizing is required for stylistic differentiation, the i element may be preferable. Example:

-
-

You simply must try the negitoro maki!

-
- -

Strong importance

-

The strong element is used to denote text with strong importance. Where bolding is used for stylistic differentiation, the b element may be preferable. Example:

-
-

Don’t stick nails in the electrical outlet.

-
- -

Small print

-

The small element is used to represent disclaimers, caveats, legal restrictions, or copyrights (commonly referred to as ‘small print’). It can also be used for attributions or satisfying licensing requirements. Example:

-
-

Copyright © 1922-2011 Acme Corporation. All Rights Reserved.

-
- -

Strikethrough

-

The s element is used to represent content that is no longer accurate or relevant. When indicating document edits i.e., marking a span of text as having been removed from a document, use the del element instead. Example:

-
-

Recommended retail price: £3.99 per bottle
Now selling for just £2.99 a bottle!

-
- -

Citations

-

The cite element is used to represent the title of a work (e.g. a book, essay, poem, song, film, TV show, sculpture, painting, musical, exhibition, etc). This can be a work that is being quoted or referenced in detail (i.e. a citation), or it can just be a work that is mentioned in passing. Example:

-
-

Universal Declaration of Human Rights, United Nations, December 1948. Adopted by General Assembly resolution 217 A (III).

-
- -

Inline quotes

-

The q element is used for quoting text inline. Example showing nested quotations:

-
-

John said, I saw Lucy at lunch, she told me Mary wants you to get some ice cream on your way home. I think I will get some at Ben and Jerry’s, on Gloucester Road.

-
- -

Definition

-

The dfn element is used to highlight the first use of a term. The title attribute can be used to describe the term. Example:

-
-

Bob’s canine mother and equine father sat him down and carefully explained that he was an allopolyploid organism.

-
- -

Abbreviation

-

The abbr element is used for any abbreviated text, whether it be acronym, initialism, or otherwise. Generally, it’s less work and useful (enough) to mark up only the first occurrence of any particular abbreviation on a page, and ignore the rest. Any text in the title attribute will appear when the user’s mouse hovers the abbreviation (although notably, this does not work in Internet Explorer for Windows). Example abbreviations:

-
-

BBC, HTML, and Staffs.

-
- -

Time

-

The time element is used to represent either a time on a 24 hour clock, or a precise date in the proleptic Gregorian calendar, optionally with a time and a time-zone offset. Example:

-
-

Queen Elizabeth II was proclaimed sovereign of each of the Commonwealth realms on and , after the death of her father, King George VI.

-
- -

Code

-

The code element is used to represent fragments of computer code. Useful for technology-oriented sites, not so useful otherwise. Example:

-
-

When you call the activate() method on the robotSnowman object, the eyes glow.

-
-

Used in conjunction with the pre element:

-
-
function getJelly() {
-    echo $aDeliciousSnack;
-}
-
- -

Variable

-

The var element is used to denote a variable in a mathematical expression or programming context, but can also be used to indicate a placeholder where the contents should be replaced with your own value. Example:

-
-

If there are n pipes leading to the ice cream factory then I expect at least n flavours of ice cream to be available for purchase!

-
- -

Sample output

-

The samp element is used to represent (sample) output from a program or computing system. Useful for technology-oriented sites, not so useful otherwise. Example:

-
-

The computer said Too much cheese in tray two but I didn’t know what that meant.

-
- -

Keyboard entry

-

The kbd element is used to denote user input (typically via a keyboard, although it may also be used to represent other input methods, such as voice commands). Example:

-

-

To take a screenshot on your Mac, press ⌘ Cmd + ⇧ Shift + 3.

-
- -

Superscript and subscript text

-

The sup element represents a superscript and the sub element represents a sub. These elements must be used only to mark up typographical conventions with specific meanings, not for typographical presentation. As a guide, only use these elements if their absence would change the meaning of the content. Example:

-
-

The coordinate of the ith point is (xi, yi). For example, the 10th point has coordinate (x10, y10).

-

f(x, n) = log4xn

-
- -

Italicised

-

The i element is used for text in an alternate voice or mood, or otherwise offset from the normal prose. Examples include taxonomic designations, technical terms, idiomatic phrases from another language, the name of a ship or other spans of text whose typographic presentation is typically italicised. Example:

-
-

There is a certain je ne sais quoi in the air.

-
- -

Emboldened

-

The b element is used for text stylistically offset from normal prose without conveying extra importance, such as key words in a document abstract, product names in a review, or other spans of text whose typographic presentation is typically emboldened. Example:

-
-

You enter a small room. Your sword glows brighter. A rat scurries past the corner wall.

-
- -

Marked or highlighted text

-

The mark element is used to represent a run of text marked or highlighted for reference purposes. When used in a quotation it indicates a highlight not originally present but added to bring the reader’s attention to that part of the text. When used in the main prose of a document, it indicates a part of the document that has been highlighted due to its relevance to the user’s current activity. Example:

-
-

I also have some kittens who are visiting me these days. They’re really cute. I think they like my garden! Maybe I should adopt a kitten.

-
- -

Edits

-

The del element is used to represent deleted or retracted text which still must remain on the page for some reason. Meanwhile its counterpart, the ins element, is used to represent inserted text. Both del and ins have a datetime attribute which allows you to include a timestamp directly in the element. Example inserted text and usage:

-
-

She bought two five pairs of shoes.

-
- -

Tabular data

-

Tables should be used when displaying tabular data. The thead, tfoot and tbody elements enable you to group rows within each a table.

-

If you use these elements, you must use every element. They should appear in this order: thead, tfoot and tbody, so that browsers can render the foot before receiving all the data. You must use these tags within the table element.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The Very Best Eggnog
IngredientsServes 12Serves 24
Milk1 quart2 quart
Cinnamon Sticks21
Vanilla Bean, Split12
Cloves510
Mace10 blades20 blades
Egg Yolks1224
Cups Sugar1 ½ cups3 cups
Dark Rum1 ½ cups3 cups
Brandy1 ½ cups3 cups
Vanilla1 tbsp2 tbsp
Half-and-half or Light Cream1 quart2 quart
Freshly grated nutmeg to taste
-
- -

Forms

-

Forms can be used when you wish to collect data from users. The fieldset element enables you to group related fields within a form, and each one should contain a corresponding legend. The label element ensures field descriptions are associated with their corresponding form widgets.

-
-
-
- Legend -
- - - Note about this field -
-
- - - Note about this field -
-
- - - Note about this field -
-
- - - Note about this field -
-
- - - Note about this field -
-
- - -
-
- - - Note about this selection -
-
- Checkbox * -
- - - -
-
-
-
- Radio - -
-
-
- - -
-
-
-
- -

This block is copyright © 2012 Paul Robert Lloyd. Code covered by the MIT license.

diff --git a/ckan/templates/development/markup.html b/ckan/templates/development/markup.html deleted file mode 100644 index 9c4fcffce30..00000000000 --- a/ckan/templates/development/markup.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'base.html' %} - -{% block page %} -
-
- {% snippet 'development/snippets/markup.html' %} -
-
-{% endblock %} diff --git a/ckan/templates/development/snippets/markup.html b/ckan/templates/development/snippets/markup.html deleted file mode 100644 index e87c0898fed..00000000000 --- a/ckan/templates/development/snippets/markup.html +++ /dev/null @@ -1,416 +0,0 @@ -

General Prose

-
-

Sections Linked

-

The main page header of this guide is an h1 element. Any header elements may include links, as depicted in the example.

-

The secondary header above is an h2 element, which may be used for any form of important page-level header. More than one may be used per page. Consider using an h2 unless you need a header level of less importance, or as a sub-header to an existing h2 element.

-

Third-Level Header Linked

-

The header above is an h3 element, which may be used for any form of page-level header which falls below the h2 header in a document hierarchy.

-

Fourth-Level Header Linked

-

The header above is an h4 element, which may be used for any form of page-level header which falls below the h3 header in a document hierarchy.

-
Fifth-Level Header Linked
-

The header above is an h5 element, which may be used for any form of page-level header which falls below the h4 header in a document hierarchy.

-
Sixth-Level Header Linked
-

The header above is an h6 element, which may be used for any form of page-level header which falls below the h5 header in a document hierarchy.

- -

Grouping content

-

Paragraphs

-

All paragraphs are wrapped in p tags. Additionally, p elements can be wrapped with a blockquote element if the p element is indeed a quote. Historically, blockquote has been used purely to force indents, but this is now achieved using CSS. Reserve blockquote for quotes.

- -

Horizontal rule

-

The hr element represents a paragraph-level thematic break, e.g. a scene change in a story, or a transition to another topic within a section of a reference book. The following extract from Pandora’s Star by Peter F. Hamilton shows two paragraphs that precede a scene change and the paragraph that follows it:

-
-

Dudley was ninety-two, in his second life, and fast approaching time for another rejuvenation. Despite his body having the physical age of a standard fifty-year-old, the prospect of a long degrading campaign within academia was one he regarded with dread. For a supposedly advanced civilization, the Intersolar Commonwearth could be appallingly backward at times, not to mention cruel.

-

Maybe it won’t be that bad, he told himself. The lie was comforting enough to get him through the rest of the night’s shift.

-
-

The Carlton AllLander drove Dudley home just after dawn. Like the astronomer, the vehicle was old and worn, but perfectly capable of doing its job. It had a cheap diesel engine, common enough on a semi-frontier world like Gralmond, although its drive array was a thoroughly modern photoneural processor. With its high suspension and deep-tread tyres it could plough along the dirt track to the observatory in all weather and seasons, including the metre-deep snow of Gralmond’s winters.

-
- -

Pre-formatted text

-

The pre element represents a block of pre-formatted text, in which structure is represented by typographic conventions rather than by elements. Such examples are an e-mail (with paragraphs indicated by blank lines, lists indicated by lines prefixed with a bullet), fragments of computer code (with structure indicated according to the conventions of that language) or displaying ASCII art. Here’s an example showing the printable characters of ASCII:

-
-
  ! " # $ % & ' ( ) * + , - . /
-0 1 2 3 4 5 6 7 8 9 : ; < = > ?
-@ A B C D E F G H I J K L M N O
-P Q R S T U V W X Y Z [ \ ] ^ _
-` a b c d e f g h i j k l m n o
-p q r s t u v w x y z { | } ~ 
-
- -

Blockquotes

-

The blockquote element represents a section that is being quoted from another source.

-
-
-

Many forms of Government have been tried, and will be tried in this world of sin and woe. No one pretends that democracy is perfect or all-wise. Indeed, it has been said that democracy is the worst form of government except all those other forms that have been tried from time to time.

-
-

Winston Churchill, in a speech to the House of Commons. 11th November 1947

-
-

Additionally, you might wish to cite the source, as in the above example. The correct method involves including the cite attribute on the blockquote element, but since no browser makes any use of that information, it’s useful to link to the source also.

- -

Ordered list

-

The ol element denotes an ordered list, and various numbering schemes are available through the CSS (including 1,2,3… a,b,c… i,ii,iii… and so on). Each item requires a surrounding <li> and </li> tag, to denote individual items within the list (as you may have guessed, li stands for list item).

-
-
    -
  1. This is an ordered list.
  2. -
  3. - This is the second item, which contains a sub list -
      -
    1. This is the sub list, which is also ordered.
    2. -
    3. It has two items.
    4. -
    -
  4. -
  5. This is the final item on this list.
  6. -
-
- -

Unordered list

-

The ul element denotes an unordered list (ie. a list of loose items that don’t require numbering, or a bulleted list). Again, each item requires a surrounding <li> and </li> tag, to denote individual items. Here is an example list showing the constituent parts of the British Isles:

-
-
    -
  • - United Kingdom of Great Britain and Northern Ireland: -
      -
    • England
    • -
    • Scotland
    • -
    • Wales
    • -
    • Northern Ireland
    • -
    -
  • -
  • Republic of Ireland
  • -
  • Isle of Man
  • -
  • - Channel Islands: -
      -
    • Bailiwick of Guernsey
    • -
    • Bailiwick of Jersey
    • -
    -
  • -
-
-

Sometimes we may want each list item to contain block elements, typically a paragraph or two.

-
-
    -
  • -

    The British Isles is an archipelago consisting of the two large islands of Great Britain and Ireland, and many smaller surrounding islands.

    -
  • -
  • -

    Great Britain is the largest island of the archipelago. Ireland is the second largest island of the archipelago and lies directly to the west of Great Britain.

    -
  • -
  • -

    The full list of islands in the British Isles includes over 1,000 islands, of which 51 have an area larger than 20 km2.

    -
  • -
-
- -

Definition list

-

The dl element is for another type of list called a definition list. Instead of list items, the content of a dl consists of dt (Definition Term) and dd (Definition description) pairs. Though it may be called a “definition list”, dl can apply to other scenarios where a parent/child relationship is applicable. For example, it may be used for marking up dialogues, with each dt naming a speaker, and each dd containing his or her words.

-
-
-
This is a term.
-
This is the definition of that term, which both live in a dl.
-
Here is another term.
-
And it gets a definition too, which is this line.
-
Here is term that shares a definition with the term below.
-
Here is a defined term.
-
dt terms may stand on their own without an accompanying dd, but in that case they share descriptions with the next available dt. You may not have a dd without a parent dt.
-
-
- -

Figures

-

Figures are usually used to refer to images:

-
-
- Example image -
-

This is a placeholder image, with supporting caption.

-
-
-
-

Here, a part of a poem is marked up using figure:

-
-
-

‘Twas brillig, and the slithy toves
- Did gyre and gimble in the wabe;
- All mimsy were the borogoves,
- And the mome raths outgrabe.

-
-

Jabberwocky (first verse). Lewis Carroll, 1832-98

-
-
-
- -

Text-level Semantics

-

There are a number of inline HTML elements you may use anywhere within other elements.

- -

Links and anchors

-

The a element is used to hyperlink text, be that to another page, a named fragment on the current page or any other location on the web. Example:

- - -

Stressed emphasis

-

The em element is used to denote text with stressed emphasis, i.e., something you’d pronounce differently. Where italicizing is required for stylistic differentiation, the i element may be preferable. Example:

-
-

You simply must try the negitoro maki!

-
- -

Strong importance

-

The strong element is used to denote text with strong importance. Where bolding is used for stylistic differentiation, the b element may be preferable. Example:

-
-

Don’t stick nails in the electrical outlet.

-
- -

Small print

-

The small element is used to represent disclaimers, caveats, legal restrictions, or copyrights (commonly referred to as ‘small print’). It can also be used for attributions or satisfying licensing requirements. Example:

-
-

Copyright © 1922-2011 Acme Corporation. All Rights Reserved.

-
- -

Strikethrough

-

The s element is used to represent content that is no longer accurate or relevant. When indicating document edits i.e., marking a span of text as having been removed from a document, use the del element instead. Example:

-
-

Recommended retail price: £3.99 per bottle
Now selling for just £2.99 a bottle!

-
- -

Citations

-

The cite element is used to represent the title of a work (e.g. a book, essay, poem, song, film, TV show, sculpture, painting, musical, exhibition, etc). This can be a work that is being quoted or referenced in detail (i.e. a citation), or it can just be a work that is mentioned in passing. Example:

-
-

Universal Declaration of Human Rights, United Nations, December 1948. Adopted by General Assembly resolution 217 A (III).

-
- -

Inline quotes

-

The q element is used for quoting text inline. Example showing nested quotations:

-
-

John said, I saw Lucy at lunch, she told me Mary wants you to get some ice cream on your way home. I think I will get some at Ben and Jerry’s, on Gloucester Road.

-
- -

Definition

-

The dfn element is used to highlight the first use of a term. The title attribute can be used to describe the term. Example:

-
-

Bob’s canine mother and equine father sat him down and carefully explained that he was an allopolyploid organism.

-
- -

Abbreviation

-

The abbr element is used for any abbreviated text, whether it be acronym, initialism, or otherwise. Generally, it’s less work and useful (enough) to mark up only the first occurrence of any particular abbreviation on a page, and ignore the rest. Any text in the title attribute will appear when the user’s mouse hovers the abbreviation (although notably, this does not work in Internet Explorer for Windows). Example abbreviations:

-
-

BBC, HTML, and Staffs.

-
- -

Time

-

The time element is used to represent either a time on a 24 hour clock, or a precise date in the proleptic Gregorian calendar, optionally with a time and a time-zone offset. Example:

-
-

Queen Elizabeth II was proclaimed sovereign of each of the Commonwealth realms on and , after the death of her father, King George VI.

-
- -

Code

-

The code element is used to represent fragments of computer code. Useful for technology-oriented sites, not so useful otherwise. Example:

-
-

When you call the activate() method on the robotSnowman object, the eyes glow.

-
-

Used in conjunction with the pre element:

-
-
function getJelly() {
-    echo $aDeliciousSnack;
-}
-
- -

Variable

-

The var element is used to denote a variable in a mathematical expression or programming context, but can also be used to indicate a placeholder where the contents should be replaced with your own value. Example:

-
-

If there are n pipes leading to the ice cream factory then I expect at least n flavours of ice cream to be available for purchase!

-
- -

Sample output

-

The samp element is used to represent (sample) output from a program or computing system. Useful for technology-oriented sites, not so useful otherwise. Example:

-
-

The computer said Too much cheese in tray two but I didn’t know what that meant.

-
- -

Keyboard entry

-

The kbd element is used to denote user input (typically via a keyboard, although it may also be used to represent other input methods, such as voice commands). Example:

-

-

To take a screenshot on your Mac, press ⌘ Cmd + ⇧ Shift + 3.

-
- -

Superscript and subscript text

-

The sup element represents a superscript and the sub element represents a sub. These elements must be used only to mark up typographical conventions with specific meanings, not for typographical presentation. As a guide, only use these elements if their absence would change the meaning of the content. Example:

-
-

The coordinate of the ith point is (xi, yi). For example, the 10th point has coordinate (x10, y10).

-

f(x, n) = log4xn

-
- -

Italicised

-

The i element is used for text in an alternate voice or mood, or otherwise offset from the normal prose. Examples include taxonomic designations, technical terms, idiomatic phrases from another language, the name of a ship or other spans of text whose typographic presentation is typically italicised. Example:

-
-

There is a certain je ne sais quoi in the air.

-
- -

Emboldened

-

The b element is used for text stylistically offset from normal prose without conveying extra importance, such as key words in a document abstract, product names in a review, or other spans of text whose typographic presentation is typically emboldened. Example:

-
-

You enter a small room. Your sword glows brighter. A rat scurries past the corner wall.

-
- -

Marked or highlighted text

-

The mark element is used to represent a run of text marked or highlighted for reference purposes. When used in a quotation it indicates a highlight not originally present but added to bring the reader’s attention to that part of the text. When used in the main prose of a document, it indicates a part of the document that has been highlighted due to its relevance to the user’s current activity. Example:

-
-

I also have some kittens who are visiting me these days. They’re really cute. I think they like my garden! Maybe I should adopt a kitten.

-
- -

Edits

-

The del element is used to represent deleted or retracted text which still must remain on the page for some reason. Meanwhile its counterpart, the ins element, is used to represent inserted text. Both del and ins have a datetime attribute which allows you to include a timestamp directly in the element. Example inserted text and usage:

-
-

She bought two five pairs of shoes.

-
- -

Tabular data

-

Tables should be used when displaying tabular data. The thead, tfoot and tbody elements enable you to group rows within each a table.

-

If you use these elements, you must use every element. They should appear in this order: thead, tfoot and tbody, so that browsers can render the foot before receiving all the data. You must use these tags within the table element.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The Very Best Eggnog
IngredientsServes 12Serves 24
Milk1 quart2 quart
Cinnamon Sticks21
Vanilla Bean, Split12
Cloves510
Mace10 blades20 blades
Egg Yolks1224
Cups Sugar1 ½ cups3 cups
Dark Rum1 ½ cups3 cups
Brandy1 ½ cups3 cups
Vanilla1 tbsp2 tbsp
Half-and-half or Light Cream1 quart2 quart
Freshly grated nutmeg to taste
-
- -

Forms

-

Forms can be used when you wish to collect data from users. The fieldset element enables you to group related fields within a form, and each one should contain a corresponding legend. The label element ensures field descriptions are associated with their corresponding form widgets.

-
-
-
- Legend -
- - - Note about this field -
-
- - - Note about this field -
-
- - - Note about this field -
-
- - - Note about this field -
-
- - - Note about this field -
-
- - -
-
- - - Note about this selection -
-
- Checkbox * -
- - - -
-
-
-
- Radio - -
-
-
- - -
-
-
-
- -

This block is copyright © 2012 Paul Robert Lloyd. Code covered by the MIT license.

diff --git a/ckan/templates/package/snippets/resource_form.html b/ckan/templates/package/snippets/resource_form.html index 9ff85d39b27..7edd1e40e8b 100644 --- a/ckan/templates/package/snippets/resource_form.html +++ b/ckan/templates/package/snippets/resource_form.html @@ -72,15 +72,17 @@ {% block previous_button %} {% endblock %} - {% block again_button %} - - {% endblock %} + {% endif %} + {% block again_button %} + + {% endblock %} + {% if stage %} {% block save_button %} - + {% endblock %} {% else %} {% block add_button %} - + {% endblock %} {% endif %}
diff --git a/ckan/tests/controllers/test_api.py b/ckan/tests/controllers/test_api.py index b36ec48c923..578adb32811 100644 --- a/ckan/tests/controllers/test_api.py +++ b/ckan/tests/controllers/test_api.py @@ -314,87 +314,3 @@ def test_jsonp_does_not_work_on_post_requests(self): eq_(res_dict['success'], True) eq_(sorted(res_dict['result']), sorted([dataset1['name'], dataset2['name']])) - - -class TestRevisionSearch(helpers.FunctionalTestBase): - - # Error cases - - def test_no_search_term(self): - app = self._get_test_app() - response = app.get('/api/search/revision', status=400) - assert_in('Bad request - Missing search term', response.body) - - def test_no_search_term_api_v2(self): - app = self._get_test_app() - response = app.get('/api/2/search/revision', status=400) - assert_in('Bad request - Missing search term', response.body) - - def test_date_instead_of_revision(self): - app = self._get_test_app() - response = app.get('/api/search/revision' - '?since_id=2010-01-01T00:00:00', status=404) - assert_in('Not found - There is no revision', response.body) - - def test_date_invalid(self): - app = self._get_test_app() - response = app.get('/api/search/revision' - '?since_time=2010-02-31T00:00:00', status=400) - assert_in('Bad request - ValueError: day is out of range for month', - response.body) - - def test_no_value(self): - app = self._get_test_app() - response = app.get('/api/search/revision?since_id=', status=400) - assert_in('Bad request - No revision specified', response.body) - - def test_revision_doesnt_exist(self): - app = self._get_test_app() - response = app.get('/api/search/revision?since_id=1234', status=404) - assert_in('Not found - There is no revision', response.body) - - def test_revision_doesnt_exist_api_v2(self): - app = self._get_test_app() - response = app.get('/api/2/search/revision?since_id=1234', status=404) - assert_in('Not found - There is no revision', response.body) - - # Normal usage - - @classmethod - def _create_revisions(cls, num_revisions): - rev_ids = [] - for i in xrange(num_revisions): - rev = model.repo.new_revision() - rev.id = text_type(i) - model.Session.commit() - rev_ids.append(rev.id) - return rev_ids - - def test_revision_since_id(self): - rev_ids = self._create_revisions(4) - app = self._get_test_app() - - response = app.get('/api/2/search/revision?since_id=%s' % rev_ids[1]) - - res = json.loads(response.body) - assert_equal(res, rev_ids[2:]) - - def test_revision_since_time(self): - rev_ids = self._create_revisions(4) - app = self._get_test_app() - - rev1 = model.Session.query(model.Revision).get(rev_ids[1]) - response = app.get('/api/2/search/revision?since_time=%s' - % rev1.timestamp.isoformat()) - - res = json.loads(response.body) - assert_equal(res, rev_ids[2:]) - - def test_revisions_returned_are_limited(self): - rev_ids = self._create_revisions(55) - app = self._get_test_app() - - response = app.get('/api/2/search/revision?since_id=%s' % rev_ids[1]) - - res = json.loads(response.body) - assert_equal(res, rev_ids[2:52]) # i.e. limited to 50 diff --git a/ckan/tests/controllers/test_package.py b/ckan/tests/controllers/test_package.py index 06bb9c58ac1..16b54381494 100644 --- a/ckan/tests/controllers/test_package.py +++ b/ckan/tests/controllers/test_package.py @@ -1011,6 +1011,71 @@ def setup_class(cls): def teardown_class(cls): p.unload('image_view') + def test_resource_view_create(self): + user = factories.User() + env = {'REMOTE_USER': user['name'].encode('ascii')} + + owner_org = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + resource = factories.Resource(package_id=dataset['id']) + + url = url_for('resource.edit_view', + id=resource['package_id'], + resource_id=resource['id'], + view_type='image_view') + + app = self._get_test_app() + response = app.post( + url, {'title': 'Test Image View'}, extra_environ=env + ).follow(extra_environ=env) + response.mustcontain('Test Image View') + + def test_resource_view_edit(self): + user = factories.User() + env = {'REMOTE_USER': user['name'].encode('ascii')} + + owner_org = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + resource = factories.Resource(package_id=dataset['id']) + + resource_view = factories.ResourceView(resource_id=resource['id']) + url = url_for('resource.edit_view', + id=resource_view['package_id'], + resource_id=resource_view['resource_id'], + view_id=resource_view['id']) + + app = self._get_test_app() + response = app.post( + url, {'title': 'Updated RV Title'}, extra_environ=env + ).follow(extra_environ=env) + response.mustcontain('Updated RV Title') + + def test_resource_view_delete(self): + user = factories.User() + env = {'REMOTE_USER': user['name'].encode('ascii')} + + owner_org = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + resource = factories.Resource(package_id=dataset['id']) + + resource_view = factories.ResourceView(resource_id=resource['id']) + url = url_for('resource.edit_view', + id=resource_view['package_id'], + resource_id=resource_view['resource_id'], + view_id=resource_view['id']) + + app = self._get_test_app() + response = app.post( + url, {'delete': 'Delete'}, extra_environ=env + ).follow(extra_environ=env) + response.mustcontain('This resource has no views') + def test_existent_resource_view_page_returns_ok_code(self): resource_view = factories.ResourceView() diff --git a/ckan/tests/legacy/functional/api/test_package_search.py b/ckan/tests/legacy/functional/api/test_package_search.py index a54c8684f63..8e32aa441ca 100644 --- a/ckan/tests/legacy/functional/api/test_package_search.py +++ b/ckan/tests/legacy/functional/api/test_package_search.py @@ -1,17 +1,12 @@ # encoding: utf-8 -from nose.tools import assert_raises -from nose.plugins.skip import SkipTest - from urllib import quote -from ckan import plugins import ckan.lib.search as search from ckan.tests.legacy import setup_test_search_index from ckan.tests.legacy.functional.api.base import * from ckan.tests.legacy import TestController as ControllerTestCase -from ckan.controllers.api import ApiController -from webob.multidict import UnicodeMultiDict + class PackageSearchApiTestCase(ApiTestCase, ControllerTestCase): @@ -31,7 +26,7 @@ def setup_class(self): 'geographic_coverage':'England, Wales'}, } CreateTestData.create_arbitrary(self.package_fixture_data) - self.base_url = self.offset('/search/dataset') + self.base_url = self.offset('/action/package_search') @classmethod def teardown_class(cls): @@ -39,60 +34,23 @@ def teardown_class(cls): search.clear_all() def assert_results(self, res_dict, expected_package_names): - expected_pkgs = [self.package_ref_from_name(expected_package_name) \ - for expected_package_name in expected_package_names] - assert_equal(set(res_dict['results']), set(expected_pkgs)) - - def test_00_read_search_params(self): - def check(request_params, expected_params): - params = ApiController._get_search_params(request_params) - assert_equal(params, expected_params) - # uri parameters - check(UnicodeMultiDict({'q': '', 'ref': 'boris'}), - {"q": "", "ref": "boris"}) - # uri json - check(UnicodeMultiDict({'qjson': '{"q": "", "ref": "boris"}'}), - {"q": "", "ref": "boris"}) - # posted json - check(UnicodeMultiDict({'{"q": "", "ref": "boris"}': u'1'}), - {"q": "", "ref": "boris"}) - check(UnicodeMultiDict({'{"q": "", "ref": "boris"}': u''}), - {"q": "", "ref": "boris"}) - # no parameters - check(UnicodeMultiDict({}), - {}) - - def test_00_read_search_params_with_errors(self): - def check_error(request_params): - assert_raises(ValueError, ApiController._get_search_params, request_params) - # uri json - check_error(UnicodeMultiDict({'qjson': '{"q": illegal json}'})) - # posted json - check_error(UnicodeMultiDict({'{"q": illegal json}': u'1'})) + result = res_dict['result']['results'][0] + assert_equal(result['name'], expected_package_names) def test_01_uri_q(self): offset = self.base_url + '?q=%s' % self.package_fixture_data['name'] res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - self.assert_results(res_dict, ['testpkg']) - assert res_dict['count'] == 1, res_dict['count'] + self.assert_results(res_dict, 'testpkg') + assert res_dict['result']['count'] == 1, res_dict['result']['count'] def test_02_post_q(self): offset = self.base_url query = {'q':'testpkg'} res = self.app.post(offset, params=query, status=200) res_dict = self.data_from_res(res) - self.assert_results(res_dict, ['testpkg']) - assert res_dict['count'] == 1, res_dict['count'] - - def test_03_uri_qjson(self): - query = {'q': self.package_fixture_data['name']} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, ['testpkg']) - assert res_dict['count'] == 1, res_dict['count'] + self.assert_results(res_dict, 'testpkg') + assert res_dict['result']['count'] == 1, res_dict['result']['count'] def test_04_post_json(self): query = {'q': self.package_fixture_data['name']} @@ -100,71 +58,40 @@ def test_04_post_json(self): offset = self.base_url res = self.app.post(offset, params=json_query, status=200) res_dict = self.data_from_res(res) - self.assert_results(res_dict, ['testpkg']) - assert res_dict['count'] == 1, res_dict['count'] - - def test_05_uri_json_tags(self): - query = {'q': 'annakarenina tags:russian tags:tolstoy'} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, [u'annakarenina']) - assert res_dict['count'] == 1, res_dict - - def test_05_uri_json_tags_multiple(self): - query = {'q': 'tags:russian tags:tolstoy'} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, [u'annakarenina']) - assert res_dict['count'] == 1, res_dict + self.assert_results(res_dict, 'testpkg') + assert res_dict['result']['count'] == 1, res_dict['result']['count'] def test_06_uri_q_tags(self): query = webhelpers.util.html_escape('annakarenina tags:russian tags:tolstoy') offset = self.base_url + '?q=%s' % query res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - self.assert_results(res_dict, [u'annakarenina']) - assert res_dict['count'] == 1, res_dict['count'] - - def test_08_uri_qjson_malformed(self): - offset = self.base_url + '?qjson="q":""' # user forgot the curly braces - res = self.app.get(offset, status=400) - self.assert_json_response(res, 'Bad request - Could not read parameters') + self.assert_results(res_dict, 'annakarenina') + assert res_dict['result']['count'] == 1, res_dict['count'] def test_09_just_tags(self): offset = self.base_url + '?q=tags:russian' res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - assert res_dict['count'] == 2, res_dict + assert res_dict['result']['count'] == 2, res_dict def test_10_multiple_tags(self): offset = self.base_url + '?q=tags:tolstoy tags:russian' res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - assert res_dict['count'] == 1, res_dict - - def test_12_all_packages_qjson(self): - query = {'q': ''} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - assert_equal(res_dict['count'], 3) + assert res_dict['result']['count'] == 1, res_dict def test_12_all_packages_q(self): offset = self.base_url + '?q=""' res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - assert_equal(res_dict['count'], 3) + assert_equal(res_dict['result']['count'], 3) def test_12_all_packages_no_q(self): offset = self.base_url res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - assert_equal(res_dict['count'], 3) + assert_equal(res_dict['result']['count'], 3) def test_12_filter_by_openness(self): offset = self.base_url + '?filter_by_openness=1' @@ -203,95 +130,6 @@ def teardown_class(cls): model.repo.rebuild_db() search.clear_all() - def test_07_uri_qjson_tags(self): - query = {'q': '', 'tags':['tolstoy']} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, [u'annakarenina']) - assert res_dict['count'] == 1, res_dict - - def test_07_uri_qjson_tags_with_flexible_query(self): - query = {'q': '', 'tags':['Flexible \u30a1']} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, [u'annakarenina', u'warandpeace']) - assert res_dict['count'] == 2, res_dict - - def test_07_uri_qjson_tags_multiple(self): - query = {'q': '', 'tags':['tolstoy', 'russian', u'Flexible \u30a1']} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - print(offset) - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, [u'annakarenina']) - assert res_dict['count'] == 1, res_dict - - def test_07_uri_qjson_tags_reverse(self): - query = {'q': '', 'tags':['russian']} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, [u'annakarenina', u'warandpeace']) - assert res_dict['count'] == 2, res_dict - - def test_07_uri_qjson_extras(self): - # TODO: solr is not currently set up to allow partial matches - # and extras are not saved as multivalued so this - # test will fail. Make extras multivalued or remove? - raise SkipTest() - - query = {"geographic_coverage":"England"} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, ['testpkg']) - assert res_dict['count'] == 1, res_dict - - def test_07_uri_qjson_extras_2(self): - query = {"national_statistic":"yes"} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, ['testpkg']) - assert res_dict['count'] == 1, res_dict - - def test_08_all_fields(self): - rating = model.Rating(user_ip_address=u'123.1.2.3', - package=self.anna, - rating=3.0) - model.Session.add(rating) - model.repo.commit_and_remove() - - query = {'q': 'russian', 'all_fields': 1} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - assert res_dict['count'] == 2, res_dict - for rec in res_dict['results']: - if rec['name'] == 'annakarenina': - anna_rec = rec - break - assert anna_rec['name'] == 'annakarenina', res_dict['results'] - assert anna_rec['title'] == 'A Novel By Tolstoy', anna_rec['title'] - assert anna_rec['license_id'] == u'other-open', anna_rec['license_id'] - assert len(anna_rec['tags']) == 3, anna_rec['tags'] - for expected_tag in ['russian', 'tolstoy', u'Flexible \u30a1']: - assert expected_tag in anna_rec['tags'], anna_rec['tags'] - - # try alternative syntax - offset = self.base_url + '?q=russian&all_fields=1' - res2 = self.app.get(offset, status=200) - assert_equal(res2.body, res.body) - def test_08_all_fields_syntax_error(self): offset = self.base_url + '?all_fields=should_be_boolean' # invalid all_fields value res = self.app.get(offset, status=400) @@ -331,139 +169,47 @@ def test_10_many_tags_with_ampersand(self): res_dict = self.data_from_res(res) assert res_dict['count'] == 1, res_dict - def test_11_pagination_limit(self): - offset = self.base_url + '?all_fields=1&q=tags:russian&limit=1&order_by=name' - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - assert res_dict['count'] == 2, res_dict - assert len(res_dict['results']) == 1, res_dict - assert res_dict['results'][0]['name'] == 'annakarenina', res_dict['results'][0]['name'] - - def test_11_pagination_offset_limit(self): - offset = self.base_url + '?all_fields=1&q=tags:russian&offset=1&limit=1&order_by=name' - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - assert res_dict['count'] == 2, res_dict - assert len(res_dict['results']) == 1, res_dict - assert res_dict['results'][0]['name'] == 'warandpeace', res_dict['results'][0]['name'] - - def test_11_pagination_syntax_error(self): - offset = self.base_url + '?all_fields=1&q="tags:russian"&start=should_be_integer&rows=1&order_by=name' # invalid offset value - res = self.app.get(offset, status=400) - print(res.body) - assert('should_be_integer' in res.body) - def test_13_just_groups(self): offset = self.base_url + '?groups=roger' res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - assert res_dict['count'] == 1, res_dict + assert res_dict['result']['count'] == 1, res_dict def test_14_empty_parameter_ignored(self): offset = self.base_url + '?groups=roger&title=' res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - assert res_dict['count'] == 1, res_dict + assert res_dict['result']['count'] == 1, res_dict class TestPackageSearchApi3(Api3TestCase, PackageSearchApiTestCase): '''Here are tests with URIs in specifically SOLR syntax.''' - def test_07_uri_qjson_tags(self): - query = {'q': 'tags:tolstoy'} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, [u'annakarenina']) - assert res_dict['count'] == 1, res_dict - - def test_07_uri_qjson_tags_with_unicode(self): - query = {'q': u'tags:"Flexible \u30a1"'} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, [u'annakarenina', u'warandpeace']) - assert res_dict['count'] == 2, res_dict - - def test_07_uri_qjson_tags_multiple(self): - query = {'q': 'tags:tolstoy tags:russian'} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - print(offset) - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, [u'annakarenina']) - assert res_dict['count'] == 1, res_dict - - def test_07_uri_qjson_tags_reverse(self): - query = {'q': 'tags:russian'} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, [u'annakarenina', u'warandpeace']) - assert res_dict['count'] == 2, res_dict - - def test_07_uri_qjson_extras_2(self): - query = {'q': "national_statistic:yes"} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - self.assert_results(res_dict, ['testpkg']) - assert res_dict['count'] == 1, res_dict - - def test_08_all_fields(self): - query = {'q': 'russian', 'fl': '*'} - json_query = self.dumps(query) - offset = self.base_url + '?qjson=%s' % json_query - res = self.app.get(offset, status=200) - res_dict = self.data_from_res(res) - assert res_dict['count'] == 2, res_dict - for rec in res_dict['results']: - if rec['name'] == 'annakarenina': - anna_rec = rec - break - assert anna_rec['name'] == 'annakarenina', res_dict['results'] - assert anna_rec['title'] == 'A Novel By Tolstoy', anna_rec['title'] - assert anna_rec['license_id'] == u'other-open', anna_rec['license_id'] - assert len(anna_rec['tags']) == 3, anna_rec['tags'] - for expected_tag in ['russian', 'tolstoy', u'Flexible \u30a1']: - assert expected_tag in anna_rec['tags'] - - # try alternative syntax - offset = self.base_url + '?q=russian&fl=*' - res2 = self.app.get(offset, status=200) - assert_equal(res2.body, res.body) - def test_09_just_tags(self): offset = self.base_url + '?q=tags:russian&fl=*' res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - assert res_dict['count'] == 2, res_dict + assert res_dict['result']['count'] == 2, res_dict def test_11_pagination_limit(self): offset = self.base_url + '?fl=*&q=tags:russian&rows=1&sort=name asc' res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - assert res_dict['count'] == 2, res_dict - assert len(res_dict['results']) == 1, res_dict - assert res_dict['results'][0]['name'] == 'annakarenina', res_dict['results'][0]['name'] + assert res_dict['result']['count'] == 2, res_dict + assert len(res_dict['result']['results']) == 1, res_dict + self.assert_results(res_dict, 'annakarenina') def test_11_pagination_offset_limit(self): offset = self.base_url + '?fl=*&q=tags:russian&start=1&rows=1&sort=name asc' res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - assert res_dict['count'] == 2, res_dict - assert len(res_dict['results']) == 1, res_dict - assert res_dict['results'][0]['name'] == 'warandpeace', res_dict['results'][0]['name'] + assert res_dict['result']['count'] == 2, res_dict + assert len(res_dict['result']['results']) == 1, res_dict + self.assert_results(res_dict, 'warandpeace') - def test_11_pagination_syntax_error(self): + def test_11_pagination_validation_error(self): offset = self.base_url + '?fl=*&q=tags:russian&start=should_be_integer&rows=1&sort=name asc' # invalid offset value - res = self.app.get(offset, status=400) - print(res.body) - assert('should_be_integer' in res.body) + res = self.app.get(offset, status=409) + assert('Validation Error' in res.body) def test_12_v1_or_v2_syntax(self): offset = self.base_url + '?all_fields=1' @@ -474,4 +220,4 @@ def test_13_just_groups(self): offset = self.base_url + '?q=groups:roger' res = self.app.get(offset, status=200) res_dict = self.data_from_res(res) - assert res_dict['count'] == 1, res_dict + assert res_dict['result']['count'] == 1, res_dict diff --git a/ckan/tests/legacy/test_coding_standards.py b/ckan/tests/legacy/test_coding_standards.py index 871a75d84a3..8488763ebe2 100644 --- a/ckan/tests/legacy/test_coding_standards.py +++ b/ckan/tests/legacy/test_coding_standards.py @@ -835,7 +835,6 @@ class TestBadExceptions(object): # and so should be translated. NASTY_EXCEPTION_BLACKLIST_FILES = [ - 'ckan/controllers/api.py', 'ckan/controllers/user.py', 'ckan/lib/mailer.py', 'ckan/logic/action/create.py', diff --git a/ckan/views/api.py b/ckan/views/api.py index 767566954e4..63240460964 100644 --- a/ckan/views/api.py +++ b/ckan/views/api.py @@ -44,7 +44,8 @@ def _finish(status_int, response_data=None, :param status_int: The HTTP status code to return :type status_int: int :param response_data: The body of the response - :type response_data: object if content_type is `text`, a string otherwise + :type response_data: object if content_type is `text` or `json`, + a string otherwise :param content_type: One of `text`, `html` or `json`. Defaults to `text` :type content_type: string :param headers: Extra headers to serve with the response @@ -82,7 +83,8 @@ def _finish_ok(response_data=None, calling this method will prepare the response. :param response_data: The body of the response - :type response_data: object if content_type is `text`, a string otherwise + :type response_data: object if content_type is `text` or `json`, + a string otherwise :param content_type: One of `text`, `html` or `json`. Defaults to `json` :type content_type: string :param resource_location: Specify this if a new resource has just been @@ -470,7 +472,7 @@ def i18n_js_translations(lang, ver=API_REST_DEFAULT_VERSION): u'base', u'i18n', u'{0}.js'.format(lang))) if not os.path.exists(source): return u'{}' - translations = open(source, u'r').read() + translations = json.load(open(source, u'r')) return _finish_ok(translations) diff --git a/ckan/views/group.py b/ckan/views/group.py index d0da298e553..18837a98515 100644 --- a/ckan/views/group.py +++ b/ckan/views/group.py @@ -178,6 +178,8 @@ def index(group_type, is_organization): sort_by = request.params.get(u'sort') # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.q = q g.sort_by_selected = sort_by @@ -228,6 +230,11 @@ def index(group_type, is_organization): extra_vars["page"].items = page_results extra_vars["group_type"] = group_type + + # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions + g.page = extra_vars["page"] return base.render(_index_template(group_type), extra_vars) @@ -246,6 +253,8 @@ def _read(id, limit, group_type): q = request.params.get(u'q', u'') # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.q = q # Search within group @@ -322,6 +331,8 @@ def pager_url(q=None, page=None): search_extras[param] = value # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.fields = fields g.fields_grouped = fields_grouped @@ -368,6 +379,8 @@ def pager_url(q=None, page=None): items_per_page=limit) # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.group_dict['package_count'] = query['count'] extra_vars["search_facets"] = g.search_facets = query['search_facets'] @@ -386,7 +399,9 @@ def pager_url(q=None, page=None): extra_vars["query_error"] = True extra_vars["page"] = h.Page(collection=[]) - # TODO: Rempve + # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.facet_titles = facets g.page = extra_vars["page"] @@ -451,6 +466,8 @@ def read(group_type, is_organization, id=None, limit=20): id=group_dict['name']) # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.q = q g.group_dict = group_dict g.group = group @@ -493,6 +510,8 @@ def activity(id, group_type, is_organization, offset=0): base.abort(400, error.message) # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.group_activity_stream = extra_vars["group_activity_stream"] g.group_dict = group_dict @@ -510,6 +529,8 @@ def about(id, group_type, is_organization): _setup_template_variables(context, {u'id': id}, group_type=group_type) # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.group_dict = group_dict g.group_type = group_type @@ -540,7 +561,9 @@ def members(id, group_type, is_organization): _(u'User %r not authorized to edit members of %s') % (g.user, id)) - # TODO:Remove + # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.members = members g.group_dict = group_dict @@ -577,6 +600,8 @@ def member_delete(id, group_type, is_organization): user_dict = _action(u'group_show')(context, {u'id': user_id}) # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.user_dict = user_dict g.user_id = user_id g.group_id = id @@ -611,6 +636,8 @@ def history(id, group_type, is_organization): error = \ _(u'Select two revisions before doing the comparison.') # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.error = error else: params[u'diff_entity'] = u'group' @@ -674,9 +701,12 @@ def history(id, group_type, is_organization): return feed.writeString(u'utf-8') # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.group_dict = group_dict g.group_revisions = group_revisions g.group = group + extra_vars = { u"group_dict": group_dict, u"group_revisions": group_revisions, @@ -739,6 +769,8 @@ def followers(id, group_type, is_organization): base.abort(403, _(u'Unauthorized to view followers %s') % u'') # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.group_dict = group_dict g.followers = followers @@ -757,6 +789,8 @@ def admins(id, group_type, is_organization): admins = authz.get_group_or_org_admin_ids(id) # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.group_dict = group_dict g.admins = admins @@ -805,6 +839,8 @@ def get(self, id, group_type, is_organization): # If no action then just show the datasets limit = 500 # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.group_dict = group_dict g.group = group extra_vars = _read(id, limit, group_type) @@ -842,6 +878,8 @@ def post(self, id, group_type, is_organization, data=None): raise Exception(u'Must be an organization') # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.group_dict = group_dict g.group = group @@ -918,6 +956,9 @@ def post(self, group_type, is_organization): try: data_dict = clean_dict( dict_fns.unflatten(tuplize_dict(parse_params(request.form)))) + data_dict.update(clean_dict( + dict_fns.unflatten(tuplize_dict(parse_params(request.files))) + )) data_dict['type'] = group_type or u'group' context['message'] = data_dict.get(u'log_message', u'') data_dict['users'] = [{u'name': g.user, u'capacity': u'admin'}] @@ -958,6 +999,8 @@ def get(self, group_type, is_organization, _group_form(group_type=group_type), extra_vars) # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.form = form extra_vars["form"] = form @@ -996,6 +1039,9 @@ def post(self, group_type, is_organization, id=None): try: data_dict = clean_dict( dict_fns.unflatten(tuplize_dict(parse_params(request.form)))) + data_dict.update(clean_dict( + dict_fns.unflatten(tuplize_dict(parse_params(request.files))) + )) context['message'] = data_dict.get(u'log_message', u'') data_dict['id'] = context['id'] context['allow_partial_update'] = True @@ -1042,6 +1088,8 @@ def get(self, id, group_type, is_organization, form = base.render(_group_form(group_type), extra_vars) # TODO: Remove + # ckan 2.9: Adding variables that were removed from c object for + # compatibility with templates in existing extensions g.grouptitle = grouptitle g.groupname = groupname g.data = data diff --git a/ckan/views/resource.py b/ckan/views/resource.py index c88bcb4eb6a..8957265bff8 100644 --- a/ckan/views/resource.py +++ b/ckan/views/resource.py @@ -611,11 +611,6 @@ def _prepare(self, id, resource_id): def post(self, package_type, id, resource_id, view_id=None): context, extra_vars = self._prepare(id, resource_id) - - to_preview = request.POST.pop(u'preview', False) - if to_preview: - context[u'preview'] = True - to_delete = request.POST.pop(u'delete', None) data = clean_dict( dict_fns.unflatten( tuplize_dict( @@ -624,7 +619,13 @@ def post(self, package_type, id, resource_id, view_id=None): ) ) data.pop(u'save', None) + + to_preview = data.pop(u'preview', False) + if to_preview: + context[u'preview'] = True + to_delete = data.pop(u'delete', None) data[u'resource_id'] = resource_id + data[u'view_type'] = request.args.get(u'view_type') try: if to_delete: @@ -662,22 +663,24 @@ def get( extra_vars.update(post_extra) package_type = _get_package_type(id) + data = extra_vars[u'data'] view_type = None # view_id exists only when updating if view_id: - try: - old_data = get_action(u'resource_view_show')( - context, { - u'id': view_id - } - ) - data = extra_vars[u'data'] or old_data - view_type = old_data.get(u'view_type') - # might as well preview when loading good existing view - if not extra_vars[u'errors']: - to_preview = True - except (NotFound, NotAuthorized): - return base.abort(404, _(u'View not found')) + if not data: + try: + data = get_action(u'resource_view_show')( + context, { + u'id': view_id + } + ) + except (NotFound, NotAuthorized): + return base.abort(404, _(u'View not found')) + + view_type = data.get(u'view_type') + # might as well preview when loading good existing view + if not extra_vars[u'errors']: + to_preview = True view_type = view_type or request.args.get(u'view_type') data[u'view_type'] = view_type diff --git a/doc/maintaining/installing/install-from-source.rst b/doc/maintaining/installing/install-from-source.rst index c5127eb4804..b27786bdeec 100644 --- a/doc/maintaining/installing/install-from-source.rst +++ b/doc/maintaining/installing/install-from-source.rst @@ -5,12 +5,11 @@ Installing CKAN from source =========================== This section describes how to install CKAN from source. Although -:doc:`install-from-package` is simpler, it requires Ubuntu 16.04 64-bit or -Ubuntu 14.04 64-bit. Installing CKAN from source works with other versions of -Ubuntu and with other operating systems (e.g. RedHat, Fedora, CentOS, OS X). If -you install CKAN from source on your own operating system, please share your -experiences on our -`How to Install CKAN `_ +:doc:`install-from-package` is simpler, it requires Ubuntu 18.04 64-bit, Ubuntu +16.04 64-bit, or Ubuntu 14.04 64-bit. Installing CKAN from source works with other +versions of Ubuntu and with other operating systems (e.g. RedHat, Fedora, CentOS, OS X). +If you install CKAN from source on your own operating system, please share your +experiences on our `How to Install CKAN `_ wiki page. From source is also the right installation method for developers who want to @@ -21,7 +20,7 @@ work on CKAN. -------------------------------- If you're using a Debian-based operating system (such as Ubuntu) install the -required packages with this command for Ubuntu 16.04:: +required packages with this command for Ubuntu 18.04 and Ubuntu 16.04:: sudo apt-get install python-dev postgresql libpq-dev python-pip python-virtualenv git-core solr-jetty openjdk-8-jdk redis-server @@ -80,6 +79,18 @@ a. Create a Python `virtual environment `_ sudo chown \`whoami\` |virtualenv| virtualenv --no-site-packages |virtualenv| |activate| + +.. note:: + + If your system uses Python3 by default (e.g. Ubuntu 18.04) make sure to create + the virtualenv using the Python2.7 executable with the ``--python`` option: + + .. parsed-literal:: + + sudo mkdir -p |virtualenv| + sudo chown \`whoami\` |virtualenv| + virtualenv --python=/usr/bin/python2.7 --no-site-packages |virtualenv| + |activate| .. important:: @@ -360,3 +371,11 @@ This is seen occasionally with Jetty and Ubuntu 14.04. It requires a solr-jetty wget https://launchpad.net/~vshn/+archive/ubuntu/solr/+files/solr-jetty-jsp-fix_1.0.2_all.deb sudo dpkg -i solr-jetty-jsp-fix_1.0.2_all.deb sudo service jetty restart + +ImportError: No module named 'flask_debugtoolbar' +------------------------------------------------- + +This may show up if you are creating the database tables and you have enabled debug +mode in the config file. Simply install the development requirements:: + + pip install -r /usr/lib/ckan/default/src/ckan/dev-requirements.txt \ No newline at end of file diff --git a/doc/maintaining/installing/solr.rst b/doc/maintaining/installing/solr.rst index 5da3851ce85..61f788b83a2 100644 --- a/doc/maintaining/installing/solr.rst +++ b/doc/maintaining/installing/solr.rst @@ -12,7 +12,22 @@ installed, we need to install and configure Solr. server, but CKAN doesn't require Jetty - you can deploy Solr to another web server, such as Tomcat, if that's convenient on your operating system. -#. Edit the Jetty configuration file (``/etc/default/jetty8`` or +.. tip:: + + Do this step only if you are using Ubuntu 18.04. + + Ubuntu 18.04 64-bit uses ``jetty9`` which does not observe the symlink created + by the Solr package. As a result, Jetty is unable to serve Solr content. To + fix this, create the symlink in the ``/var/lib/jetty9/webapps/`` directory:: + + sudo ln -s /etc/solr/solr-jetty.xml /var/lib/jetty9/webapps/solr.xml + + The Jetty port value must also be changed on ``jetty9``. To do that, edit the + ``jetty.port`` value in ``/etc/jetty9/start.ini``:: + + jetty.port=8983 # (line 23) + +#. Edit the Jetty configuration file (``/etc/default/jetty8(9)`` or ``/etc/default/jetty``) and change the following variables:: NO_START=0 # (line 4) @@ -28,6 +43,10 @@ installed, we need to install and configure Solr. Start or restart the Jetty server. + For Ubuntu 18.04:: + + sudo service jetty9 restart + For Ubuntu 16.04:: sudo service jetty8 restart @@ -68,6 +87,10 @@ installed, we need to install and configure Solr. Now restart Solr: + For Ubuntu 18.04:: + + sudo service jetty9 restart + For Ubuntu 16.04:: sudo service jetty8 restart @@ -82,5 +105,4 @@ installed, we need to install and configure Solr. #. Finally, change the :ref:`solr_url` setting in your :ref:`config_file` (|production.ini|) to point to your Solr server, for example:: - solr_url=http://127.0.0.1:8983/solr - + solr_url=http://127.0.0.1:8983/solr \ No newline at end of file diff --git a/doc/theming/templates.rst b/doc/theming/templates.rst index 277d2701dd2..3b5f236cc64 100644 --- a/doc/theming/templates.rst +++ b/doc/theming/templates.rst @@ -679,7 +679,7 @@ use the right HTML tags and CSS classes. There are two places to look for CSS classes available in CKAN: -1. The `Bootstrap 2.3.2 docs `_. All of the HTML, CSS and JavaScript +1. The `Bootstrap 3.3.7 docs `_. All of the HTML, CSS and JavaScript provided by Bootstrap is available to use in CKAN. 2. CKAN's development primer page, which can be found on any CKAN site at