Django SelectDateWidget to show month and year only - django

Is there a different widget or argument that will allow django to only show/take the year and month input instead of year, month and day?
Currently using SelectDateWidget.

There's a snippet here, which sets the day to 1 (presuming you've got a DateField that this value will end up in, you'll need to get some kind of day).
The code is like this (just in case Django snippets disappears):
import datetime
import re
from django.forms.widgets import Widget, Select
from django.utils.dates import MONTHS
from django.utils.safestring import mark_safe
__all__ = ('MonthYearWidget',)
RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')
class MonthYearWidget(Widget):
"""
A Widget that splits date input into two <select> boxes for month and year,
with 'day' defaulting to the first of the month.
Based on SelectDateWidget, in
django/trunk/django/forms/extras/widgets.py
"""
none_value = (0, '---')
month_field = '%s_month'
year_field = '%s_year'
def __init__(self, attrs=None, years=None, required=True):
# years is an optional list/tuple of years to use in the "year" select box.
self.attrs = attrs or {}
self.required = required
if years:
self.years = years
else:
this_year = datetime.date.today().year
self.years = range(this_year, this_year+10)
def render(self, name, value, attrs=None):
try:
year_val, month_val = value.year, value.month
except AttributeError:
year_val = month_val = None
if isinstance(value, basestring):
match = RE_DATE.match(value)
if match:
year_val, month_val, day_val = [int(v) for v in match.groups()]
output = []
if 'id' in self.attrs:
id_ = self.attrs['id']
else:
id_ = 'id_%s' % name
month_choices = MONTHS.items()
if not (self.required and value):
month_choices.append(self.none_value)
month_choices.sort()
local_attrs = self.build_attrs(id=self.month_field % id_)
s = Select(choices=month_choices)
select_html = s.render(self.month_field % name, month_val, local_attrs)
output.append(select_html)
year_choices = [(i, i) for i in self.years]
if not (self.required and value):
year_choices.insert(0, self.none_value)
local_attrs['id'] = self.year_field % id_
s = Select(choices=year_choices)
select_html = s.render(self.year_field % name, year_val, local_attrs)
output.append(select_html)
return mark_safe(u'\n'.join(output))
def id_for_label(self, id_):
return '%s_month' % id_
id_for_label = classmethod(id_for_label)
def value_from_datadict(self, data, files, name):
y = data.get(self.year_field % name)
m = data.get(self.month_field % name)
if y == m == "0":
return None
if y and m:
return '%s-%s-%s' % (y, m, 1)
return data.get(name, None)

A Python 3 widget sample here https://djangosnippets.org/snippets/10522/.
Example usage :
class myForm(forms.Form):
# ...
date = forms.DateField(
required=False,
widget=MonthYearWidget(years=xrange(2004,2010))
)

I came across the same problem today and solved it by removing the day field via a css property and setting 1 as value for the day on clean up.
#id_my_date_field_day-button {
display: none;
}
I used a ModelForm with an UpdateView and therefore had initial data in my fields which made life a bit simpler because I always had a valid value for the day of my_date_field.

I've written a simpler version (https://djangosnippets.org/snippets/10943/) inheriting from django built-in SelectDateWidget.
In widgets.py:
import calendar
import datetime
from django.forms.widgets import HiddenInput, SelectDateWidget
from django.utils import datetime_safe
from django.utils.formats import get_format
class MonthYearWidget(SelectDateWidget):
def __init__(self, last_day=False, *args, **kwargs):
self.last_day = last_day
return super().__init__(*args, **kwargs)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
day_name = self.day_field % name
day_subwidget = HiddenInput().get_context(
name=day_name,
value=1,
attrs={**context["widget"]["attrs"], "id": "id_%s" % day_name},
)
context["widget"]["subwidgets"][0] = day_subwidget["widget"]
return context
def value_from_datadict(self, data, files, name):
value = super().value_from_datadict(data, files, name)
if self.last_day is True:
y = data.get(self.year_field % name)
m = data.get(self.month_field % name)
if y is not None and m is not None:
input_format = get_format("DATE_INPUT_FORMATS")[0]
monthrange = calendar.monthrange(int(y), int(m))
date_value = datetime.date(int(y), int(m), monthrange[1])
date_value = datetime_safe.new_date(date_value)
return date_value.strftime(input_format)
return value
kwargs:
last_day : if set to True, returns the last day of the month in the generated date, otherwise returns a date starting from the 1st day of the month
Usage example:
# models.py
from django.db import models
from django.utils.translation import gettext_lazy as _
class MyModel(models.Model):
start = models.DateField(
_("Start date"),
)
end = models.DateField(
_("End date"),
)
class Meta:
verbose_name = _("My model")
# forms.py
from django import forms
from .models import MyModel
from .widgets import MonthYearWidget
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = []
widgets = {
"start": MonthYearWidget(),
"end": MonthYearWidget(last_day=True),
}

Related

can't compare datetime.datetime to datetime.date django

I'm trying to build reservation app. I tried to find the solution but I have no idea.
I want to compare date of reservation from check_availibility with date from form. In form I've made:
check_in = forms.DateField(required=True, input_formats=["%Y-%m-%dT%H:%M", ])
check_out = forms.DateField(required=True, input_formats=["%Y-%m-%dT%H:%M", ])
import datetime
def check_availability(room, check_in, check_out):
avail_list = []
booking_list = Booking.objects.filter(room=room)
for booking in booking_list:
if booking.check_in > check_out or booking.check_out < check_in:
avail_list.append(True)
else:
avail_list.append(False)
return all(avail_list)
from hotelbooking.booking_funkctions.availibility import check_availability
class BookingView(FormView):
form_class = AvalilabilityForm
template_name = 'availability_form.html'
def form_valid(self, form):
data = form.cleaned_data
room_list = Room.objects.filter(category=data['room_category'])
available_rooms=[]
for room in room_list:
if check_availability(room, data['check_in'], data['check_out']):
available_rooms.append(room)
if len(available_rooms)>0:
room = available_rooms[0]
booking = Booking.objects.create(
user = self.request.user,
room = room,
check_in = data['check_in'],
check_out = data['check_out']
)
booking.save()
return HttpResponse(booking)
else:
return HttpResponse('this category of rooms are booked')
Since you're cleaning the date it should already convert the date strings to date objects, so you can do this:
def check_availability(room, check_in, check_out):
avail_list = []
booking_list = Booking.objects.filter(room=room)
for booking in booking_list:
if booking.check_in.date() > check_out.date() or booking.check_out.date() < check_in.date():
avail_list.append(True)
else:
avail_list.append(False)
return all(avail_list)
Just call the .date() method for the datetime one:
from datetime import datetime, date
is_today_today = datetime.now().date() == date.today() # valid
print(is_today_today) # True # (the only time that is real is the present)

Where am i going wrong, trying to recursively scrape?

I want to scrape a site with scrapy that lists its products in catagories i'm new to scrapy and just getting my head round it today but though i was getting the gist of it on simple scrapes so attempted to scrape urls and return them to scrape further but appears i'm missing something.
someone answered fixing my code here is the latest version as thought i'd have another go at learning scrapy today but its still not recursively scanning it just seems to loop through all the pages but never gets into parse the items
never seems to enter the else statement
yield scrapy.Request(url = response.url,callback = self.parse_item)
i can debug it to check the items are parsed correctly if i force it to output items without looping
if i change the following
if product_pages:
for product_url in product_pages:
product_url2 = str(self.base_url + product_url)
self.log("Queued up: %s" % product_url2)
yield scrapy.Request(url = product_url2,callback = self.parse_product_pages)
else:
yield scrapy.Request(url = response.url,callback = self.parse_item)
to
if product_pages:
for product_url in product_pages:
product_url2 = str(self.base_url + product_url)
self.log("Queued up: %s" % product_url2)
yield scrapy.Request(url = product_url2,callback = self.parse_item)
else:
yield scrapy.Request(url = response.url,callback = self.parse_product_pages)
here is my code i'm working in python 2.7
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.selector import HtmlXPathSelector
from ybscrape.items import Product
from scrapy.linkextractors import LinkExtractor
from scrapy.linkextractors.sgml import SgmlLinkExtractor
class ybracingSpider(CrawlSpider):
name = 'ybscrape2'
download_delay = 0.75
def __init__(self, *args, **kwargs):
super(ybracingSpider, self).__init__(*args, **kwargs)
self.allowed_domains = ['http://www.ybracing.com/', 'www.ybracing.com', 'www.esellepro.com']
self.base_url = 'http://www.ybracing.com'
self.start_urls = ['http://www.ybracing.com/karting/']
def parse_start_url(self, response):
category = response.xpath("//h2/a/#href").extract()
#loop over catagory pages take the product link and add all pages url
for product in category:
all_pages = '?itemsperpage=99999'
category_url = str(self.base_url + product + all_pages)
self.log("Queued up: %s" % category_url)
yield scrapy.Request(url = category_url,callback = self.parse_product_pages)
def parse_product_pages(self, response):
product_pages = response.xpath("//li/div/div/h3/a/#href").extract()
#print("debug pause")
#print(product_pages)
#wait = input("PRESS ENTER TO CONTINUE.")
#print("continue")
if product_pages:
for product_url in product_pages:
product_url2 = str(self.base_url + product_url)
self.log("Queued up: %s" % product_url2)
yield scrapy.Request(url = product_url2,callback = self.parse_product_pages)
else:
yield scrapy.Request(url = response.url,callback = self.parse_item)
def parse_item(self, response):
item = Product()
item['description'] = response.xpath("//div[#id='Tabbed-Container-Details']/div[2]/div/text()").extract()
item['product_title'] = response.xpath("//h3[#class='Product-Heading']/text()").extract()
item['price'] = response.xpath("//div[#id='Product-Price']/text()").extract()
table_rows = response.xpath("//table[#id='SpecificationTab']/tr[*]/td[1]//text()").extract()
yield item
my items.py
from scrapy.item import Item, Field
class Product(Item):
product_title = Field()
description = Field()
price = Field()
What i'm expecting my code to do in steps
grab all the links within the the first export (categories) (this works)
look at all 9999 products inside each category and export the list (this works)
take the product url from the export append it to the base url to get to the product page for each. (this works)
4.then read data from in the product page to add to items ( never gets here) unlese i skip the if statement but thats not recursive it wont handle sub catagories like that
Here, I have made some changes in your code and now it's working
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.selector import HtmlXPathSelector
from demo.items import DemoItem
from scrapy.linkextractors import LinkExtractor
from scrapy.linkextractors.sgml import SgmlLinkExtractor
class DemoSpider(CrawlSpider):
name = 'ybracing2'
def __init__(self, *args, **kwargs):
super(DemoSpider, self).__init__(*args, **kwargs)
self.allowed_domains = ['http://www.ybracing.com/', 'www.ybracing.com', 'www.esellepro.com']
self.base_url = 'http://www.ybracing.com'
self.start_urls = ['http://www.ybracing.com/racewear/']
def parse_start_url(self, response):
category = response.xpath("//h2/a/#href").extract()
#loop over catagory pages take the product link and add all pages url
for product in category:
all_pages = '?itemsperpage=99999'
category_url = str(self.base_url + product + all_pages)
self.log("Queued up: %s" % category_url)
yield scrapy.Request(url = category_url,callback = self.parse_product_pages)
def parse_product_pages(self, response):
product_pages = response.xpath("//div[#class='Product']/a/#href").extract()
if product_pages:
for product_url in product_pages:
product_url2 = str(self.base_url + product_url)
self.log("Queued up: %s" % product_url2)
yield scrapy.Request(url = product_url2,callback = self.parse_item)
else:
yield scrapy.Request(url = response.url,callback = self.parse_product_pages)
def parse_item(self, response):
item = DemoItem()
dirty_data ={}
item['product_title'] = response.xpath("//h3[#class='Product-Heading']/text()").extract()
item['price'] = response.xpath("//div[#id='Product-Price']/text()").extract()
item['description'] = response.xpath("//div[#id='Tabbed-Container-Details']/div[2]/div/text()").extract()
#image['product_image'] =
# for variable in dirty_data.keys():
# if dirty_data[variable]:
# if variable == 'price':
# item[variable] = float(''.join(dirty_data[variable]).strip().replace('$', '').replace(',', ''))
# else:
# item[variable] = ''.join(dirty_data[variable]).strip()
yield item

Create + Update with custom widget

I have created a custom widget that holds "SelecTimeWidget" & "SelectDateWidget"
It works fine when i'm creating a new event but when i turn it into a (UpdateView)
I get an error
global name 'to_current_timezone' is not defined
I don't know how to go about this to allow the widget to be used in the creation and edit of an event.
class EventUpdateView(UpdateView):
form_class = CreateEvent
model = Event
class EventCreateView(CreateView):
form_class = CreateEvent
model = Event
def form_valid(self, form):
Event = form.save(commit=False)
Event.created_by = self.request.user
Event.save()
return HttpResponseRedirect('/calendar/')
class CreateEvent(forms.ModelForm):
class Meta:
model = Event
fields = ['title', 'start', 'end', 'description', 'category']
widgets = {
'start': SelectDateTimeWidget(date_format= '%m/%m/%Y'),
'end': SelectDateTimeWidget(date_format= '%d/%m/%Y')
}
The Widget, obviously SelectTimeWidget is also a custom one but the error is leading me to "value = to_current_timezone(value)" this line within the code below
class SelectDateTimeWidget(forms.MultiWidget):
supports_microseconds = False
def __init__(self, attrs=None, date_format=None, time_format=None):
widgets = (SelectDateWidget(empty_label=( "Year", "Month", "Day")),
SelectTimeWidget(use_seconds=False))
super(SelectDateTimeWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
value = to_current_timezone(value)
return [value.date(), value.time().replace(microsecond=0)]
return [None, None]
def subwidgets(self, name, value, attrs=None):
if self.is_localized:
for widget in self.widgets:
widget.is_localized = self.is_localized
# value is a list of values, each corresponding to a widget
# in self.widgets.
if not isinstance(value, list):
value = self.decompress(value)
output = []
final_attrs = self.build_attrs(attrs)
id_ = final_attrs.get('id')
for i, widget in enumerate(self.widgets):
try:
widget_value = value[i]
except IndexError:
widget_value = None
if id_:
final_attrs = dict(final_attrs, id='%s_%s' % (id_, i))
output.append(widget.render(name + '_%s' % i, widget_value, final_attrs))
return output
Does Anyone know a way around this?
"from django.forms.util import to_current_timezone"
I did not realise To_current_timezone was an import, once i found this it worked :)
Love answering my own question after some time worrying about it

Django admin list_filter - filter field by is empty (None or empty string "")

In models.py I have:
class User(modals.Model):
name = models.CharField(max_length=255)
image = models.ImageField(blank=True, null=True)
And in admin.py:
class UserAdmin(admin.ModelAdmin):
list_filter = ['image']
admin.site.register(User, UserAdmin)
I just want to filter Users by have image or not (null or empty string)
But django shows filter by image urls =)
Is there a way to make list_filter = ['image'] behave like boolean field?
Big thx for advices!
admin.py
class ImageListFilter(admin.SimpleListFilter):
title = _('Has photo')
parameter_name = 'has_photo'
def lookups(self, request, model_admin):
return (
('yes', _('Yes')),
('no', _('No')),
)
def queryset(self, request, queryset):
if self.value() == 'yes':
return queryset.filter(image__isnull=False).exclude(image='')
if self.value() == 'no':
return queryset.filter(Q(image__isnull=True) | Q(image__exact=''))
class UserAdmin(admin.ModelAdmin):
list_filter = [ImageListFilter]
As of Django 3.1, you can use EmptyFieldListFilter as follows:
list_filter = (
("my_fk_field", admin.EmptyFieldListFilter),
)
See the docs for details. See the release notes as well.
The code is available here, if you need to customize it or backport it.
Based on MaxCore's answer, I created customised class that I can use to modify title and parameter name:
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q
class NotNullFilter(admin.SimpleListFilter):
title = _('Filter title not set')
parameter_name = 'parameter name not set'
def lookups(self, request, model_admin):
return (
('not_null', _('Not empty only')),
('null', _('Empty only')),
)
def queryset(self, request, queryset):
if self.value() == 'not_null':
is_null_false = {
self.parameter_name + "__isnull": False
}
exclude = {
self.parameter_name: ""
}
return queryset.filter(**is_null_false).exclude(**exclude)
if self.value() == 'null':
is_null_true = {
self.parameter_name + "__isnull": True
}
param_exact = {
self.parameter_name + "__exact": ""
}
return queryset.filter(Q(**is_null_true) | Q(**param_exact))
class YoutubeNotNullFilter(NotNullFilter):
title = "Youtube"
parameter_name = "youtube_videoid"
K.H.'s approach of a base class that could be reused easily was really helpful for me. I couldn't get the written example to work, but with a small tweak it worked perfectly (Python 2.7, Django 1.10) to achieve this.
from django.contrib import admin
class NotNullFilter(admin.SimpleListFilter):
title = 'Filter title not set'
parameter_name = 'parameter name not set'
def lookups(self, request, model_admin):
return (
('not_null', 'Not empty only'),
('null', 'Empty only'),
)
def queryset(self, request, queryset):
filter_string = self.parameter_name + '__isnull'
if self.value() == 'not_null':
is_null_false = {
filter_string: False
}
return queryset.filter(**is_null_false)
if self.value() == 'null':
is_null_true = {
filter_string: True
}
return queryset.filter(**is_null_true)
class YoutubeNotNullFilter(NotNullFilter):
title = "Youtube"
parameter_name = "youtube_videoid"
class SomeVideoClass(admin.ModelAdmin):
...
list_filter = [YouTubeNotNullFilter, ...]
A bit more flexible version based on MaxCore's answer, which creates new classes on-the-fly
def by_null_filter(attr, name, null_label='is Null', non_null_label='not Null', bool_dt=False, bool_value=False):
class ByNullFilter(admin.SimpleListFilter):
"""List display filter to show null/not null values"""
parameter_name = attr
title = name
def lookups(self, request, model_admin):
if bool_dt:
label_null = 'not %s' % attr
label_non_null = attr
elif bool_value:
label_null = 'no'
label_non_null = 'yes'
else:
label_null = null_label
label_non_null = non_null_label
return (
('not_null', label_non_null),
('null', label_null)
)
def queryset(self, request, queryset):
filter_string = attr + '__isnull'
if self.value() == 'not_null':
is_null_false = {
filter_string: False
}
return queryset.filter(**is_null_false)
if self.value() == 'null':
is_null_true = {
filter_string: True
}
return queryset.filter(**is_null_true)
return ByNullFilter
Usage
if you have processed DateTime field and you want to filter on it - does it have value or null
# with default filter labels (`not Null`, `is Null`)
list_filter = (by_null_filter('processed', 'Processed'), )
# Processed header with labels based on field name (`processed`, `not processed`)
list_filter = (by_null_filter('processed', 'Processed', bool_dt=True), )
# Paid header filter with labels based on bool(end_time) (`yes`, `no`)
list_filter = (by_null_filter('end_time', 'Paid', bool_value=True), )

django_ct filter for django haystack not returning results

I've upgraded haystack but I'm getting an issue with using django_ct I've indexed the model for movie with python manage.py update_index movies but when I try to get a filter by model using either of the two
result = SearchQuerySet().raw_search('django_ct:movies.movie')
result = SearchQuerySet().filter(django_ct='movies.movie')
I get no result
>>> from haystack.query import SearchQuerySet
>>> result = SearchQuerySet().filter(django_ct='movies.movie')
>>> len(result.all())
0
>>> len(result)
0
>>> result = SearchQuerySet().raw_search('django_ct:movies.movie')
>>> len(result)
0
>>> len(result.all())
0
>>> all_result = SearchQuerySet()
>>> len(all_result)
10924
>>>
The index for Movies is
import datetime
from django.db.models import Q
from haystack import indexes
from search.base_search_index import BaseSearchIndex
from search.fields import MultiValueDateField
from movies.models import Movie, Show
class MovieIndex(BaseSearchIndex, indexes.Indexable):
dates = MultiValueDateField(null=True,)
areas = indexes.MultiValueField(null=True)
categories = indexes.MultiValueField(null=True)
venues = indexes.MultiValueField(null=True)
hot = indexes.BooleanField(model_attr='is_hot')
showing = indexes.BooleanField(model_attr='now_showing')
sortorder = indexes.IntegerField(model_attr='sortorder')
name = indexes.CharField(model_attr='name')
def prepare_areas(self, obj):
shows = obj.shows.all()
areas = []
if shows:
for s in shows:
if s.venue and s.venue.area_id:
areas.append(s.venue.area_id)
return self.get_list_for_multivalue_field(areas)
def prepare_venues(self, obj):
shows = obj.shows.all()
if shows:
return [show.venue_id for show in shows]
return None
def prepare_categories(self, obj):
cats = []
for category in obj.categories.all():
cats.extend([c.id for c in category.get_ancestors(include_self=True)])
return self.get_list_for_multivalue_field(cats)
def prepare_dates(self, obj):
dates = [d for d in obj.start_dates()]
return dates
def get_model(self):
return Movie
def index_queryset(self, using=None):
"""Used when the entire index for model is updated."""
return self.get_model().objects.filter(
Q(visible=True) &
Q(publish__lte=datetime.date.today()) &
(Q(expires__isnull=True) | Q(expires__gte=datetime.date.today())) &
Q(shows__starts__gte=datetime.date.today()),
).distinct()
class ShowIndex(BaseSearchIndex, indexes.Indexable):
movie = indexes.IntegerField(null=True)
venue = indexes.IntegerField(null=True)
starts = indexes.DateField(model_attr='starts')
area = indexes.IntegerField(null=True,)
categories = indexes.MultiValueField(null=True)
hot = indexes.BooleanField()
i = indexes.IntegerField(model_attr='movie__pk')
shows = indexes.IntegerField(model_attr='pk')
def prepare_movie(self, obj):
if obj.movie_id:
return obj.movie_id
return -1
def prepare_venue(self, obj):
if obj.venue_id:
return obj.venue_id
return -1
def prepare_area(self, obj):
if obj.venue.area_id:
return obj.venue.area_id
return -1
def prepare_categories(self, obj):
cats = []
for category in obj.movie.categories.all():
cats.extend([c.id for c in category.get_ancestors(include_self=True)])
return self.get_list_for_multivalue_field(cats)
def prepare_hot(self, obj):
return obj.movie.is_hot()
def prepare_slug(self, obj):
return u''
def prepare(self, obj):
today = datetime.datetime.now()
data = super(BaseSearchIndex, self).prepare(obj)
data['boost'] = 1
return data
def get_model(self):
return Show
def index_queryset(self, using=None):
"""Used when the entire index for model is updated."""
return self.get_model().objects.filter(starts__gte=datetime.date.today()).distinct()
and it's inheritance
import datetime
from django.db.models import Q
from haystack import indexes
class BaseSearchIndex(indexes.SearchIndex):
text = indexes.CharField(document=True, use_template=True)
html = indexes.CharField(use_template=True, indexed=False)
json = indexes.CharField(use_template=True, indexed=False)
slug = indexes.CharField()
#pk_xapian = indexes.IntegerField(model_attr='pk')
i = indexes.IntegerField(model_attr='pk')
def prepare_slug(self, obj):
return u'unique%sunique' % obj.slug
def get_updated_field(self):
return 'modified'
def index_queryset(self, using=None):
"""Used when the entire index for model is updated."""
today = datetime.date.today()
return self.get_model().objects.filter(
Q(visible=True) &
Q(publish__lte=today) &
(Q(expires__isnull=True) | Q(expires__gte=today))
).distinct()
def prepare(self, obj):
today = datetime.datetime.now()
data = super(BaseSearchIndex, self).prepare(obj)
weeks = (today - obj.created).days / 7 + 1
data['boost'] = 1 + (1.0 / weeks)
return data
def get_list_for_multivalue_field(self, value):
if len(value) == 0:
return []
elif len(value) == 1:
return [value[0],]
return value
The search works but the logic used to get the data to json for an app has the logic with searchqueryset and filters per model and returns nothing.