mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-15 08:17:08 +08:00
Generic sitemap (#1198)
* Add test for both issue cases * Use generic type and add new django 4.1 `get_latest_lastmod` method * Add Sitemap to monkeypatch * Update test case to use generic + add failing case * Test GenericSitemap too
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Mapping, Optional, Sequence, Union
|
||||
from typing import Any, Dict, Generic, Iterable, List, Mapping, Optional, Sequence, TypeVar, Union
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.sites.requests import RequestSite
|
||||
@@ -13,33 +13,39 @@ class SitemapNotFound(Exception): ...
|
||||
|
||||
def ping_google(sitemap_url: Optional[str] = ..., ping_url: str = ..., sitemap_uses_https: bool = ...) -> None: ...
|
||||
|
||||
class Sitemap:
|
||||
_ItemT = TypeVar("_ItemT")
|
||||
|
||||
class Sitemap(Generic[_ItemT]):
|
||||
limit: int = ...
|
||||
protocol: Optional[str] = ...
|
||||
i18n: bool = ...
|
||||
languages: Optional[Sequence[str]] = ...
|
||||
alternates: bool = ...
|
||||
x_default: bool = ...
|
||||
def items(self) -> Union[Sequence[Any], QuerySet[Any]]: ...
|
||||
def location(self, item: Model) -> str: ...
|
||||
def items(self) -> Iterable[_ItemT]: ...
|
||||
def location(self, item: _ItemT) -> str: ...
|
||||
@property
|
||||
def paginator(self) -> Paginator: ...
|
||||
def get_urls(
|
||||
self, page: Union[int, str] = ..., site: Optional[Union[Site, RequestSite]] = ..., protocol: Optional[str] = ...
|
||||
) -> List[Dict[str, Any]]: ...
|
||||
def get_latest_lastmod(self) -> Optional[datetime]: ...
|
||||
|
||||
class GenericSitemap(Sitemap):
|
||||
_ModelT = TypeVar("_ModelT", bound=Model)
|
||||
|
||||
class GenericSitemap(Sitemap[_ModelT]):
|
||||
priority: Optional[float] = ...
|
||||
changefreq: Optional[str] = ...
|
||||
queryset: QuerySet[Model] = ...
|
||||
queryset: QuerySet[_ModelT] = ...
|
||||
date_field: Optional[str] = ...
|
||||
protocol: Optional[str] = ...
|
||||
def __init__(
|
||||
self,
|
||||
info_dict: Mapping[str, Union[datetime, QuerySet[Model], str]],
|
||||
info_dict: Mapping[str, Union[datetime, QuerySet[_ModelT], str]],
|
||||
priority: Optional[float] = ...,
|
||||
changefreq: Optional[str] = ...,
|
||||
protocol: Optional[str] = ...,
|
||||
) -> None: ...
|
||||
def lastmod(self, item: Model) -> Optional[datetime]: ...
|
||||
def items(self) -> QuerySet[Model]: ...
|
||||
def items(self) -> QuerySet[_ModelT]: ...
|
||||
def lastmod(self, item: _ModelT) -> Optional[datetime]: ...
|
||||
def get_latest_lastmod(self) -> Optional[datetime]: ...
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import Any, Generic, Iterable, List, Optional, Tuple, Type, TypeVar
|
||||
from django import VERSION as VERSION
|
||||
from django.contrib.admin import ModelAdmin
|
||||
from django.contrib.admin.options import BaseModelAdmin
|
||||
from django.contrib.sitemaps import Sitemap
|
||||
from django.contrib.syndication.views import Feed
|
||||
from django.core.files.utils import FileProxyMixin
|
||||
from django.core.paginator import Paginator
|
||||
@@ -60,6 +61,7 @@ _need_generic: List[MPGeneric[Any]] = [
|
||||
MPGeneric(BaseModelForm),
|
||||
MPGeneric(BaseModelFormSet),
|
||||
MPGeneric(Feed),
|
||||
MPGeneric(Sitemap),
|
||||
MPGeneric(FileProxyMixin),
|
||||
MPGeneric(Lookup),
|
||||
# These types do have native `__class_getitem__` method since django 3.1:
|
||||
|
||||
80
tests/typecheck/contrib/sitemaps/test_generic_sitemap.yml
Normal file
80
tests/typecheck/contrib/sitemaps/test_generic_sitemap.yml
Normal file
@@ -0,0 +1,80 @@
|
||||
- case: test_items_custom_model
|
||||
main: |
|
||||
from django.contrib.sitemaps import GenericSitemap, Sitemap
|
||||
from django.contrib.sitemaps.views import sitemap
|
||||
from django.db.models import QuerySet
|
||||
from django.urls import path, reverse
|
||||
from myapp.models import Offer
|
||||
|
||||
class OfferSitemap(Sitemap[Offer]):
|
||||
priority = 1
|
||||
changefreq = "always"
|
||||
|
||||
def items(self) -> QuerySet[Offer]:
|
||||
return Offer.objects.all()
|
||||
|
||||
def location(self, item: Offer) -> str:
|
||||
return reverse(
|
||||
"myapp:detail-offer",
|
||||
kwargs={
|
||||
"provider_name": item.provider,
|
||||
"offer_name": item.trc, # E: "Offer" has no attribute "trc"
|
||||
},
|
||||
)
|
||||
|
||||
class WrongOfferSitemap(Sitemap[Offer]):
|
||||
def items(self) -> str:
|
||||
return "Yes"
|
||||
def location(self, item: str) -> int:
|
||||
return 1
|
||||
|
||||
info_dict = {"queryset": Offer.objects.all()}
|
||||
broken_info_dict = {"queryset": [1, 2]}
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
'sitemap.xml', sitemap,
|
||||
{'sitemaps': {'offers': GenericSitemap[Offer](info_dict, priority=0.6)}},
|
||||
name='django.contrib.sitemaps.views.sitemap'
|
||||
),
|
||||
path(
|
||||
'broken_sitemap.xml', sitemap,
|
||||
{'sitemaps': {'offers': GenericSitemap[Offer](broken_info_dict, priority=0.6)}},
|
||||
name='django.contrib.sitemaps.views.broken_sitemap'
|
||||
),
|
||||
]
|
||||
out: |
|
||||
main:24: error: Return type "str" of "items" incompatible with return type "Iterable[Offer]" in supertype "Sitemap"
|
||||
main:26: error: Argument 1 of "location" is incompatible with supertype "Sitemap"; supertype defines the argument type as "Offer"
|
||||
main:26: note: This violates the Liskov substitution principle
|
||||
main:26: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
|
||||
main:26: error: Return type "int" of "location" incompatible with return type "str" in supertype "Sitemap"
|
||||
main:40: error: Argument 1 to "GenericSitemap" has incompatible type "Dict[str, List[int]]"; expected "Mapping[str, Union[datetime, _QuerySet[Offer, Offer], str]]"
|
||||
|
||||
installed_apps:
|
||||
- myapp
|
||||
files:
|
||||
- path: myapp/__init__.py
|
||||
- path: myapp/models.py
|
||||
content: |
|
||||
from django.db import models
|
||||
|
||||
class Offer(models.Model):
|
||||
provider = models.ForeignKey("self", on_delete=models.CASCADE)
|
||||
url_name = models.CharField()
|
||||
|
||||
- case: test_items_string_sequence
|
||||
main: |
|
||||
from django.contrib.sitemaps import Sitemap
|
||||
from typing import List
|
||||
from django.urls import reverse
|
||||
|
||||
class StaticViewSitemap(Sitemap[str]):
|
||||
priority = 1
|
||||
changefreq = "always"
|
||||
|
||||
def items(self) -> List[str]:
|
||||
return ["home", "about", "contact", "recommendations", "privacy-policy", "blog"]
|
||||
|
||||
def location(self, item: str) -> str:
|
||||
return reverse(item)
|
||||
Reference in New Issue
Block a user