1import os
2from datetime import date
3
4from lektor.context import Context
5from lektor.db import get_alts
6
7
8def test_root(pad):
9    record = pad.root
10
11    assert record is not None
12    assert record["title"] == "Welcome"
13    assert record["_template"] == "page.html"
14    assert record["_alt"] == "_primary"
15    assert record["_slug"] == ""
16    assert record["_id"] == ""
17    assert record["_path"] == "/"
18
19
20def test_project_implied_model(pad):
21    project = pad.query("/projects").first()
22    assert project is not None
23    assert project["_model"] == "project"
24
25
26def test_child_query_visibility_setting(pad):
27    projects = pad.get("/projects")
28    assert not projects.children._include_hidden
29
30    project_query = pad.query("/projects")
31    assert project_query._include_hidden
32    assert not project_query._include_undiscoverable
33
34
35def test_alt_fallback(pad):
36    # page that is missing a german tranlation
37    wolf_page = pad.get("/projects/wolf", alt="de")
38
39    # Falls back to primary
40    assert wolf_page.alt == "de"
41    assert wolf_page["_source_alt"] == "_primary"
42    assert wolf_page["name"] == "Wolf"
43
44    # If we ask for the alts of that page, we will only get english
45    assert get_alts(wolf_page) == ["en"]
46
47    # Unless we include fallbacks in which case we will also see german
48    # show up in the list.
49    assert get_alts(wolf_page, fallback=True) == ["en", "de"]
50
51
52def test_alt_parent(pad):
53    wolf_page = pad.get("/projects/wolf", alt="de")
54    assert wolf_page.alt == "de"
55    assert wolf_page.alt == wolf_page.parent.alt
56
57
58def test_url_matching_with_customized_slug_in_alt(pad):
59    en = pad.resolve_url_path("/projects/slave/")
60    assert en.alt == "en"
61    assert en["_source_alt"] == "_primary"
62    assert en.path == "/projects/slave"
63
64    de = pad.resolve_url_path("/de/projects/sklave/")
65    assert de.alt == "de"
66    assert de["_source_alt"] == "de"
67    assert de.path == "/projects/slave"
68
69    assert get_alts(en) == ["en", "de"]
70
71
72def test_basic_alts(pad):
73    with Context(pad=pad):
74        assert get_alts() == ["en", "de"]
75
76
77def test_basic_query_syntax(pad, F):
78    projects = pad.get("/projects")
79
80    encumbered = (
81        projects.children.filter((F._slug == "master") | (F._slug == "slave"))
82        .order_by("_slug")
83        .all()
84    )
85
86    assert len(encumbered) == 2
87    assert [x["name"] for x in encumbered] == ["Master", "Slave"]
88
89
90def test_basic_query_syntax_template(pad, eval_expr):
91    projects = pad.get("/projects")
92
93    encumbered = eval_expr(
94        """
95        this.children.filter(
96            (F._slug == 'master').or(F._slug == 'slave')
97        ).order_by('_slug')
98    """,
99        pad=pad,
100        this=projects,
101    ).all()
102
103    assert len(encumbered) == 2
104    assert [x["name"] for x in encumbered] == ["Master", "Slave"]
105
106
107def test_is_child_of(pad):
108    projects = pad.get("/projects")
109    assert projects.is_child_of(projects)
110    assert not projects.is_child_of(projects, strict=True)
111    child = projects.children.first()
112    assert child.is_child_of(projects)
113    assert child.is_child_of(projects, strict=True)
114
115
116def test_undiscoverable_basics(pad):
117    projects = pad.query("/projects")
118    assert projects.count() == 8
119    assert projects.include_undiscoverable(True).count() == 9
120    assert pad.get("/projects").children.count() == 8
121    assert "secret" not in [x["_id"] for x in pad.get("/projects").children]
122    assert not projects._include_undiscoverable
123    assert projects._include_hidden
124
125    secret = pad.get("/projects/secret")
126    assert secret.is_undiscoverable
127    assert secret.url_path == "/projects/secret/"
128
129    q = secret.children
130    assert q._include_undiscoverable is False
131    assert q._include_hidden is None
132    q = q.include_undiscoverable(True)
133    assert q._include_undiscoverable is True
134    assert q._include_hidden is False
135
136    secret = pad.resolve_url_path("/projects/secret")
137    assert secret is not None
138    assert secret.path == "/projects/secret"
139
140
141def test_attachment_api(pad):
142    from lektor.db import Image, Video
143
144    root = pad.root
145    root_attachments = [
146        "hello.txt",
147        "test-progressive.jpg",
148        "test-sof-last.jpg",
149        "test.jpg",
150        "test.mp4",
151    ]
152    assert root.attachments.count() == len(root_attachments)
153    assert sorted(x["_id"] for x in root.attachments) == root_attachments
154
155    txt = root.attachments.get("hello.txt")
156    assert txt is not None
157    assert txt["_attachment_type"] == "text"
158    assert txt.url_path == "/hello.txt"
159
160    img = root.attachments.get("test.jpg")
161    assert img is not None
162    assert img["_attachment_type"] == "image"
163    assert isinstance(img, Image)
164    assert img.url_path == "/test.jpg"
165
166    video = root.attachments.get("test.mp4")
167    assert video is not None
168    assert video["_attachment_type"] == "video"
169    assert isinstance(video, Video)
170    assert video.url_path == "/test.mp4"
171
172
173def test_query_normalization(pad):
174    projects = pad.get("projects")
175    assert pad.get("projects") is projects
176    assert pad.get("/projects") is projects
177    assert pad.get("/projects/.") is projects
178    assert pad.get("//projects/.") is projects
179
180
181def test_distinct(pad):
182    posts = pad.query("blog")
183    distinct_categories = posts.distinct("category")
184    assert isinstance(distinct_categories, set)
185    assert distinct_categories == set(["My Category"])
186    distinct_tags = posts.distinct("tags")
187    assert isinstance(distinct_tags, set)
188    assert distinct_tags == set(["tag1", "tag2", "tag3"])
189    distinct_pub_dates = posts.distinct("pub_date")
190    assert distinct_pub_dates == set([date(2015, 12, 12), date(2015, 12, 13)])
191    assert posts.distinct("foo") == set()
192    # Post 2 has no summary; check we don't include Undefined in distinct().
193    assert posts.distinct("summary") == set(["hello"])
194
195
196def test_root_pagination(scratch_project, scratch_env):
197    base = scratch_project.tree
198    with open(os.path.join(base, "models", "page.ini"), "w") as f:
199        f.write(
200            "[model]\n"
201            "label = {{ this.title }}\n\n"
202            "[children]\n"
203            "model = page\n"
204            "[pagination]\n"
205            "enabled = yes\n"
206            "per_page = 1\n"
207            "[fields.title]\n"
208            "type = string\n"
209            "[fields.body]\n"
210            "type = markdown\n"
211        )
212
213    for name in "a", "b", "c":
214        os.mkdir(os.path.join(base, "content", name))
215        with open(os.path.join(base, "content", name, "contents.lr"), "w") as f:
216            f.write(
217                "_model: page\n"
218                "---\n"
219                "title: Page %s\n"
220                "---\n"
221                "body: Hello World!\n" % name
222            )
223
224    from lektor.db import Database
225
226    scratch_pad = Database(scratch_env).new_pad()
227
228    root = scratch_pad.root
229    assert root.children.count() == 3
230
231    root_1 = scratch_pad.resolve_url_path("/")
232    assert root_1.page_num == 1
233
234    root_2 = scratch_pad.resolve_url_path("/page/2/")
235    assert root_2.page_num == 2
236
237
238def test_undefined_order(pad):
239    # A missing value should sort after all others.
240    blog_post = pad.db.datamodels["blog-post"]
241
242    from lektor.db import Query
243
244    class TestQuery(Query):
245        def _iterate(self):
246            for day, pub_date in [
247                (3, "2016-01-03"),
248                (4, None),  # No pub_date.
249                (1, "2016-01-01"),
250                (2, "2016-01-02"),
251            ]:
252                yield pad.instance_from_data(
253                    {"_id": str(day), "_path": "test/%s" % day, "pub_date": pub_date},
254                    datamodel=blog_post,
255                )
256
257    ids = [c["_id"] for c in TestQuery("test", pad).order_by("pub_date")]
258    assert ["4", "1", "2", "3"] == ids
259
260    ids = [c["_id"] for c in TestQuery("test", pad).order_by("-pub_date")]
261    assert ["3", "2", "1", "4"] == ids
262
263
264def test_hidden_flag(pad):
265    # This page is just not hidden at all
266    post = pad.get("blog/post1")
267    assert not post.is_hidden
268
269    # The root is never hidden itself unless forced
270    root = pad.get("/")
271    assert not root.is_hidden
272
273    # The container is hidden
274    container = pad.get("extra/container")
275    assert container.is_hidden
276
277    # But the child of the container is not
278    a = pad.get("extra/container/a")
279    assert not a.is_hidden
280    assert container.children.all() == [a]
281
282    # Attachments are also always visible
283    attachment = pad.get("extra/container/hello.txt")
284    assert not attachment.is_hidden
285
286
287def test_default_order_by(scratch_project, scratch_env):
288    from lektor.db import Database
289
290    tree = scratch_project.tree
291    with open(os.path.join(tree, "models", "mymodel.ini"), "w") as f:
292        f.write(
293            "[children]\n"
294            "order_by = title\n"
295            "[attachments]\n"
296            "order_by = attachment_filename\n"
297        )
298    os.mkdir(os.path.join(tree, "content", "myobj"))
299    with open(os.path.join(tree, "content", "myobj", "contents.lr"), "w") as f:
300        f.write("_model: mymodel\n" "---\n" "title: My Test Object\n")
301
302    pad = Database(scratch_env).new_pad()
303    myobj = pad.get("/myobj")
304    children = myobj.children
305    assert list(children.get_order_by()) == ["title"]
306    assert list(children.order_by("explicit").get_order_by()) == ["explicit"]
307    assert list(myobj.attachments.get_order_by()) == ["attachment_filename"]
308
309
310def test_offset_without_limit_query(pad):
311    projects = pad.get("/projects")
312
313    x = projects.children.offset(1).order_by("_slug").first()
314
315    assert x["name"] == "Coffee"
316