from odoo import fields, models, api, _ from odoo.exceptions import UserError, ValidationError import requests from collections import defaultdict from datetime import datetime import logging _logger = logging.getLogger(__name__) class WeatherAPI(models.Model): _name = 'weather.api' _description = 'Weather API Integration' _rec_name = 'company_id' # Thêm trường mới để cấu hình timeout api_timeout = fields.Integer( string="API Timeout (seconds)", default=10, help="Timeout for API requests in seconds" ) company_id = fields.Many2one( 'res.company', string='Công ty', required=True, default=lambda self: self.env.company ) current_temperature = fields.Float(string="Nhiệt độ hiện tại", compute="_compute_weather", store=False) current_humidity = fields.Float(string="Độ ẩm hiện tại", compute="_compute_weather", store=False) current_wind_speed = fields.Float(string="Gió hiện tại", compute="_compute_weather", store=False) current_description = fields.Char(string="Tình trạng thời tiết", compute="_compute_weather", store=False) today_hourly_first = fields.Html(string="1h - 8h", compute="_compute_weather", store=False) today_hourly_second = fields.Html(string="9h - 16h", compute="_compute_weather", store=False) today_hourly_third = fields.Html(string="17h - 24h", compute="_compute_weather", store=False) next_3_days_weather = fields.Html(string="Dự báo 3 ngày", compute="_compute_weather", store=False) @api.depends() def _compute_weather(self): for rec in self: try: current = rec.get_weather_current() rec.current_temperature = current.get('temperature') rec.current_humidity = current.get('humidity') rec.current_wind_speed = current.get('wind_speed') rec.current_description = current.get('description') # Weather by time blocks today = rec._get_today_weather() rec.today_hourly_first = rec._format_html(today['first']) rec.today_hourly_second = rec._format_html(today['second']) rec.today_hourly_third = rec._format_html(today['third']) # Next 3 days forecast = rec._get_next_3days_forecast() rec.next_3_days_weather = rec._format_3days_html(forecast) except Exception as e: _logger.error("Error computing weather data: %s", str(e)) # Set default values when error occurs rec._set_default_weather_values() def _set_default_weather_values(self): """Set default values when API fails""" self.update({ 'current_temperature': 0.0, 'current_humidity': 0.0, 'current_wind_speed': 0.0, 'current_description': _("Không thể lấy dữ liệu thời tiết"), 'today_hourly_first': "

Không có dữ liệu

", 'today_hourly_second': "

Không có dữ liệu

", 'today_hourly_third': "

Không có dữ liệu

", 'next_3_days_weather': "

Không có dữ liệu

", }) def _format_html(self, data): if not data: return "

Không có dữ liệu.

" html = "" return html def _format_3days_html(self, forecast_data): if not forecast_data: return "

Không có dữ liệu.

" html = """
""" for day in forecast_data: html += f"""

{day['date']}

Tình trạng: {day['main_description']}

Nhiệt độ: {day['temp_min']}°C - {day['temp_max']}°C

Gió: {day['wind_speed']} m/s

Độ ẩm: {day['humidity']}%


""" html += "
" return html @api.model def get_api_key(self): """Lấy thông tin API key từ cấu hình với kiểm tra bảo mật""" api_key = self.env['ir.config_parameter'].sudo().get_param('weather_infor.api_key') if not api_key: raise ValidationError(_("API Key chưa được cấu hình. Vui lòng vào Settings > Weather API Settings để nhập API Key.")) return api_key @api.model def get_city(self): """Lấy thông tin thành phố từ cấu hình công ty với validation""" city = self.env.company.city if not city: raise ValidationError(_("Thành phố chưa được cấu hình. Vui lòng vào Settings > Công ty để nhập thành phố.")) return city.strip() def _make_api_request(self, url): """Helper method to make API requests with proper error handling""" try: response = requests.get( url, timeout=self.api_timeout, headers={'User-Agent': 'Odoo/1.0'} ) response.raise_for_status() # Raises HTTPError for bad responses return response.json() except requests.exceptions.Timeout: _logger.error("Weather API timeout after %s seconds", self.api_timeout) raise UserError(_("Dịch vụ thời tiết không phản hồi. Vui lòng thử lại sau.")) except requests.exceptions.RequestException as e: _logger.error("Weather API request failed: %s", str(e)) raise UserError(_("Không thể kết nối đến dịch vụ thời tiết. Vui lòng kiểm tra kết nối mạng.")) except ValueError as e: # Includes JSON decode errors _logger.error("Invalid JSON response from weather API: %s", str(e)) raise UserError(_("Dữ liệu thời tiết nhận được không hợp lệ.")) @api.model def get_weather_current(self): """Lấy dữ liệu thời tiết hiện tại với xử lý lỗi tốt hơn""" try: api_key = self.get_api_key() city = self.get_city() url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric&lang=vi" data = self._make_api_request(url) return { 'temperature': data['main']['temp'], 'humidity': data['main']['humidity'], 'wind_speed': data['wind']['speed'], 'description': data['weather'][0]['description'].capitalize() } except KeyError as e: _logger.error("Missing expected data in weather API response: %s", str(e)) raise UserError(_("Dữ liệu thời tiết không đầy đủ. Vui lòng thử lại sau.")) @api.model def _get_next_3days_forecast(self): """Lấy dữ liệu thời tiết cho 3 ngày tiếp theo với xử lý dữ liệu tốt hơn""" try: api_key = self.get_api_key() city = self.get_city() url = f"https://api.openweathermap.org/data/2.5/forecast?q={city}&appid={api_key}&units=metric&lang=vi" data = self._make_api_request(url) forecast_data = data['list'] # Gom dữ liệu theo ngày weather_data = defaultdict(list) for forecast in forecast_data: try: dt_txt = forecast['dt_txt'] date_str = dt_txt.split(' ')[0] weather_data[date_str].append(forecast) except (KeyError, AttributeError) as e: _logger.warning("Invalid forecast data structure: %s", str(e)) continue # Chọn 3 ngày tiếp theo (bỏ qua hôm nay) today = datetime.now().date().strftime('%Y-%m-%d') sorted_dates = sorted([d for d in weather_data.keys() if d != today]) if not sorted_dates: return [] next_3_days = sorted_dates[:3] # Lấy 3 ngày đầu tiên sau hôm nay result = [] for date in next_3_days: daily_forecasts = weather_data[date] if not daily_forecasts: continue temps = [f['main']['temp'] for f in daily_forecasts] humidities = [f['main']['humidity'] for f in daily_forecasts] descriptions = [f['weather'][0]['description'] for f in daily_forecasts] wind_speeds = [f['wind']['speed'] for f in daily_forecasts] result.append({ 'date': date, 'temp_min': min(temps), 'temp_max': max(temps), 'humidity': round(sum(humidities) / len(humidities), 2), 'wind_speed': round(sum(wind_speeds) / len(wind_speeds), 2), 'main_description': max(set(descriptions), key=descriptions.count).capitalize(), }) return result except Exception as e: _logger.error("Error getting 3-day forecast: %s", str(e)) raise UserError(_("Không thể lấy dự báo thời tiết. Vui lòng thử lại sau.")) @api.model def _get_today_weather(self): """Lấy dữ liệu thời tiết ngày hôm nay chia làm 3 khung giờ""" try: api_key = self.get_api_key() city = self.get_city() url = f"https://api.openweathermap.org/data/2.5/forecast?q={city}&appid={api_key}&units=metric&lang=vi" data = self._make_api_request(url) forecast_data = data['list'] today_str = datetime.now().strftime('%Y-%m-%d') hourly_data = { 'first': [], # 1-8h 'second': [], # 9-16h 'third': [] # 17-24h } for forecast in forecast_data: try: dt_txt = forecast['dt_txt'] date_part, time_part = dt_txt.split(' ') if date_part != today_str: continue hour = int(time_part.split(':')[0]) weather_info = { 'hour': hour, 'date': date_part, 'temp_min': forecast['main']['temp_min'], 'temp_max': forecast['main']['temp_max'], 'humidity': forecast['main']['humidity'], 'wind_speed': forecast['wind']['speed'], 'description': forecast['weather'][0]['description'].capitalize() } if 0 <= hour <= 8: hourly_data['first'].append(weather_info) elif 9 <= hour <= 16: hourly_data['second'].append(weather_info) else: hourly_data['third'].append(weather_info) except (KeyError, ValueError) as e: _logger.warning("Invalid hourly forecast data: %s", str(e)) continue return hourly_data except Exception as e: _logger.error("Error getting today's weather: %s", str(e)) return { 'first': [], 'second': [], 'third': [] }